//! Rust 测试框架驱动的源码类集成测试:从前端/编译器到 VM 的宿主构造、绑定方法、字段检查及 GC。 //! execute/try_execute 按现有 globals 声明可用宿主名,加载临时代码,执行后统一归还代码根。 use compiler::{CompileOptions, compile_source}; use endor_core::{ ContextNativeFunction, EndorTypeObject, NativeCallable, NativeContext, NativeResult, ObjectPayload, TypeConstructor, VmError, }; use endor_core::{ObjectRef, Value, Vm}; use frontend::{EndorReader, ParseMode}; #[derive(Debug)] /// 原生实例载荷,WidgetFactory 分配;字符串是宿主标签,没有需要 trace 的托管对象边。 struct NativeWidget(String); impl ObjectPayload for NativeWidget { /// Store::payload 的类型检查通过 Any 下转,测试据此读取原生标签。 fn as_any(&self) -> &dyn std::any::Any { self } /// 对象种类诊断入口使用稳定标签 widget,与测试载荷检查保持一致。 fn kind_name(&self) -> &'static str { "widget" } /// Store 属性协议查询此开关;允许在原生实例上存放源码类声明的动态字段。 fn allows_attributes(&self) -> bool { true } } #[derive(Debug)] /// NativeTyped 构造策略使用的宿主回调;源码子类沿继承链复用它,但实际实例附着请求的子类。 struct WidgetFactory; impl NativeCallable for WidgetFactory { /// VM 原生调用入口调用;参数依次为请求的 Type、宿主标签、声明字段值,结果由 NativeContext 返回协议接管。 fn invoke(&self, cx: &mut NativeContext<'_>, args: &[Value]) -> Result { // 解构 NativeTyped 调用布局;类型对象与标签必须存在,其余切片作为字段参数。 let [ Value::Object(requested), Value::Text(native_label), values @ .., ] = args else { return Err(VmError::InvalidArgument("widget constructor arguments")); }; // 复制请求类型的完整字段元数据,以便稍后可变访问 Store 并分配实例。 let fields = cx .store() .payload::(*requested, "Type")? .fields() .to_vec(); // 字段数量必须匹配,错误时还没有分配新实例。 if values.len() != fields.len() { return Err(VmError::InvalidArgument("widget field count")); } // 先校验字段注解,避免无效实参进入实例分配和字段写入阶段。 cx.store().check_class_field_arguments(*requested, values)?; // 通过当前原生上下文分配载荷;发生后续错误时由上下文清理临时分配根。 let instance = cx.allocate(NativeWidget(native_label.clone()))?; // 附着实际请求的 Type,而非固定 Widget,保持 Child/Grand 的名义身份。 cx.attach_type(instance, *requested)?; // 按声明字段顺序写值,Value clone 仍由 Store 属性更新建立内部引用边。 for (field, value) in fields.iter().zip(values) { cx.set_attr(instance, &field.name, value.clone())?; } Ok(NativeResult::Return(Value::Object(instance))) } } #[test] /// 由 Rust 测试框架调用:验证源码两级继承仍由原生工厂分配载荷,字段约束和名义父类型保留,结果根独立保活类型图。 fn source_subclasses_preserve_native_allocation_fields_and_nominal_identity() { // 隔离本场景堆、类型配置与执行状态,最后检查没有遗留对象。 let mut vm = Vm::new().unwrap(); // 开启源码注解及字段检查,下面的类型错误才会按约束拒绝。 vm.set_source_type_checks(true).unwrap(); // 测试持环境初始根,源码函数和类型声明可在内部引用此环境。 let globals = vm.new_dict().unwrap(); // 引导得到 Object/Type 拥有根,注册环境后分别归还测试根。 let roots = vm.bootstrap_types().unwrap(); // 注册 NativeContext 回调,后续通过 NativeTyped 类型构造协议调用。 let factory = vm .allocate(ContextNativeFunction::new("widget", WidgetFactory, vec![])) .unwrap(); // 创建宿主 Widget 类型,以 Object 为父类并保存原生构造器引用。 let widget = vm .new_type( "Widget", roots.object, TypeConstructor::NativeTyped(factory), ) .unwrap(); // 把引导类型/宿主类型放入源码可访问的环境,再归还各自外部根。 for (name, object) in [ ("Object", roots.object), ("Type", roots.type_type), ("Widget", widget), ] { vm.dict_set(globals, Value::Text(name.into()), Value::Object(object)) .unwrap(); vm.release(object).unwrap(); } // Widget 已持有工厂内部边,测试不再额外持工厂根。 vm.release(factory).unwrap(); // Child 增加数值字段,Grand 再增加文本字段,两层源码继承都沿用 Widget 原生分配策略。 let source = "class Child: Widget { value: Number }\nclass Grand: Child { label: String }"; assert_eq!( execute(&mut vm, globals, source, ParseMode::Module), Value::Nil ); // 检查确实由运行时字段类型约束拒绝,而非返回一个看似成功的实例。 assert!(matches!( try_execute( &mut vm, globals, r#"Grand("native", "bad", "extra")"#, ParseMode::Expression ), Err(VmError::TypeMismatch { .. }) )); // 实际调用 Grand 得到拥有实例结果,随后独立于 globals 验证其原生载荷及继承字段。 let value = execute( &mut vm, globals, "Grand(\"native\", 9, \"extra\")", ParseMode::Expression, ); // 成功结果必须是托管实例;此句柄持有 execute 返回的拥有根。 let Value::Object(instance) = value else { panic!("native instance") }; assert_eq!( vm.payload::(instance, "widget").unwrap().0, "native" ); assert!(vm.is_instance(instance, widget).unwrap()); assert_eq!( vm.get_attr(instance, "value").unwrap(), Value::Number(9_i64.into()) ); assert_eq!( vm.get_attr(instance, "label").unwrap(), Value::Text("extra".into()) ); // 先清环境的全局拥有边,打断函数/类型与环境之间可能的引用环。 vm.dict_clear(globals).unwrap(); // 归还环境外部根,剩余图是否存活只由其他拥有根决定。 vm.release(globals).unwrap(); // 收集已无外部根的类型/函数/环境环;仍由实例或回调拥有的图必须保留。 vm.collect_cycles(); assert!(vm.is_instance(instance, widget).unwrap()); // 归还实例拥有结果根,之后其类型链及内部图也应可清理。 vm.release(instance).unwrap(); // 收集已无外部根的类型/函数/环境环;仍由实例或回调拥有的图必须保留。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } /// 成功路径测试复用的包装,try_execute 的 VM 错误直接使该断言场景失败;返回 Value 归调用方释放。 fn execute(vm: &mut Vm, globals: ObjectRef, source: &str, mode: ParseMode) -> Value { try_execute(vm, globals, source, mode).unwrap() } /// 类测试共用的编译/执行桥接,解析与加载失败直接使测试失败,VM 执行错误则原样返回给错误用例。 /// globals 是借用环境,返回对象值有独立拥有根;无论执行成功或失败,本辅助函数都归还加载的代码根。 fn try_execute( vm: &mut Vm, globals: ObjectRef, source: &str, mode: ParseMode, ) -> Result { // 从当前环境条目提取文本键作为编译期已知名;这只声明名称,不复制运行时值。 let names = vm .dict_items(globals) .unwrap() .iter() // 非文本键不能作为源码标识符,忽略它们而不向编译器提供伪名称。 .filter_map(|(name, _)| { // 只把文本键转成声明名,其余键留在环境但不参与源码解析。 if let Value::Text(name) = name { Some(name.clone()) } else { None } }) .collect(); // 为临时源码添加测试来源身份,错误诊断据此显示文件名与位置。 let source = EndorReader::from_text("", source); // 根据当前名字列表编译,前端或语义失败属于测试设置错误而非期望的 VM 错误。 let template = compile_source(&source, mode, &CompileOptions { globals: names }).unwrap(); // 显式加载模板取得当前 VM 的拥有代码根,禁止跨堆复用对象句柄。 let code = template.load(vm).unwrap(); // 先保存 Result,随后即使执行失败也必须释放当前代码根。 let result = vm.execute(code, globals); // 临时代码的拥有根归还,成功结果若含对象仍有自己的根。 vm.release(code).unwrap(); result } #[test] /// 由 Rust 测试框架调用:验证继承方法绑定真实子类实例、绑定参数数目扣除 self,单独回调根在清空环境后仍能动态派发。 fn source_inheritance_binding_and_dynamic_dispatch_keep_the_real_receiver_alive() { // 隔离本场景堆、类型配置与执行状态,最后检查没有遗留对象。 let mut vm = Vm::new().unwrap(); // 测试持环境初始根,源码函数和类型声明可在内部引用此环境。 let globals = vm.new_dict().unwrap(); // 引导得到 Object/Type 拥有根,注册环境后分别归还测试根。 let roots = vm.bootstrap_types().unwrap(); // 把引导类型/宿主类型放入源码可访问的环境,再归还各自外部根。 for (name, object) in [("Object", roots.object), ("Type", roots.type_type)] { vm.dict_set(globals, Value::Text(name.into()), Value::Object(object)) .unwrap(); vm.release(object).unwrap(); } // Base.via 通过 self.read 动态派发;Child 覆盖 read,withEvent 用于检查绑定后的参数数量。 let source = r#" class Base { value: Number function read(self: Base): Number { return self.value } function via(self: Base): Number { return self.read() } function withEvent(self: Base, event: Object): Object { return event } } class Child: Base { label: String function read(self: Child): Number { return 12 } } "#; assert_eq!( execute(&mut vm, globals, source, ParseMode::Module), Value::Nil ); // 读取临时 Child 实例的继承方法 via,返回绑定方法拥有根,方法必须保存实际接收者。 let callback = execute( &mut vm, globals, "Child(9, \"ok\").via", ParseMode::Expression, ); // 回调必须是托管绑定方法,拥有根一直保留到环境清理与实际调用之后。 let Value::Object(callback) = callback else { panic!("bound callback") }; assert_eq!( vm.object_store() .positional_parameter_count(callback) .unwrap(), Some(0) ); // 另取带 event 参数的绑定方法,检查 self 自动绑定后仍剩一个位置参数。 let with_event = execute( &mut vm, globals, "Child(9, \"ok\").withEvent", ParseMode::Expression, ); // 将第二个回调结果解构为拥有句柄,稍后单独释放。 let Value::Object(with_event) = with_event else { panic!("bound callback") }; assert_eq!( vm.object_store() .positional_parameter_count(with_event) .unwrap(), Some(1) ); // 参数计数检查结束,归还第二个绑定方法,避免它掩盖主回调的保活责任。 vm.release(with_event).unwrap(); // 先清环境的全局拥有边,打断函数/类型与环境之间可能的引用环。 vm.dict_clear(globals).unwrap(); // 归还环境外部根,剩余图是否存活只由其他拥有根决定。 vm.release(globals).unwrap(); // 收集已无外部根的类型/函数/环境环;仍由实例或回调拥有的图必须保留。 vm.collect_cycles(); // 绑定回调是接收者最后的外部可达入口;Base.via 内的 self.read 应派发到 Child.read。 // No global name or external instance root remains. The bound callback // alone retains its receiver and can still dispatch to Child.read. assert_eq!( vm.call(callback, &[]).unwrap(), Value::Number(12_i64.into()) ); // 完成清空环境后的动态派发验证,归还最后回调根。 vm.release(callback).unwrap(); // 收集已无外部根的类型/函数/环境环;仍由实例或回调拥有的图必须保留。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] /// 由 Rust 测试框架调用:验证重复字段及缺显式 self 在语义分析阶段被拒绝,不需创建 VM 或执行源码。 fn duplicate_members_and_missing_receivers_fail_before_execution() { // 两个独立语义错误案例:同类重复字段,以及方法没有显式 self 参数。 for text in [ "class Bad { value: Number value: String }", "class Bad { function read(): Number { return 1 } }", ] { // 为临时源码添加测试来源身份,错误诊断据此显示文件名与位置。 let source = EndorReader::from_text("", text); assert!( compile_source( &source, ParseMode::Module, &CompileOptions { globals: vec!["Object".into()] } ) .is_err() ); } } #[test] /// 由 Rust 测试框架调用:验证继承字段构造/赋值/删除检查、失败前后版本不变、动态名义注解重绑定与清理。 fn declared_field_checks_cover_construction_inheritance_writes_and_gc() { // 隔离本场景堆、类型配置与执行状态,最后检查没有遗留对象。 let mut vm = Vm::new().unwrap(); // 开启源码注解及字段检查,下面的类型错误才会按约束拒绝。 vm.set_source_type_checks(true).unwrap(); // 测试持环境初始根,源码函数和类型声明可在内部引用此环境。 let globals = vm.new_dict().unwrap(); // 引导得到 Object/Type 拥有根,注册环境后分别归还测试根。 let roots = vm.bootstrap_types().unwrap(); vm.dict_set( globals, Value::Text("Object".into()), Value::Object(roots.object), ) .unwrap(); vm.release(roots.object).unwrap(); vm.release(roots.type_type).unwrap(); // 一次模块声明建立继承字段、名义对象字段及未知注解三种约束,真正的构造/写入在后面分别触发。 execute( &mut vm, globals, r#" class Base { count: Number } class Child: Base { label: String } class Holder { child: Base } class Unknown { value: Missing } "#, ParseMode::Module, ); // 每份交互源码只提交一条语句:先登记 Child 实例,再让 Holder 的字段持有同一实例。 // Interactive intentionally accepts one complete statement per source unit. execute( &mut vm, globals, r#"value = Child(9, "ok")"#, ParseMode::Interactive, ); execute( &mut vm, globals, "holder = Holder(value)", ParseMode::Interactive, ); // 从 globals 借用已登记实例;测试不额外持该句柄的拥有根。 let Value::Object(value) = vm .dict_get(globals, &Value::Text("value".into())) .unwrap() .unwrap() else { panic!("instance") }; // 记录合法实例当前版本,后续失败的字段写入不得改变它。 let version = vm.object_store().change_version(value).unwrap(); // 覆盖继承字段、子字段、名义对象字段、未知注解及错误赋值,错误必须在更新前返回。 for source in [ r#"Child("bad", "ok")"#, "Child(9, 2)", "Holder(1)", "Unknown(1)", r#"value.count = "bad""#, ] { // 错误应是类型不匹配或未知注解,不接受其他无关失败掩盖约束问题。 assert!(matches!( try_execute(&mut vm, globals, source, ParseMode::Interactive), Err(VmError::TypeMismatch { .. } | VmError::UnknownAnnotation(_)) )); } assert_eq!( vm.get_attr(value, "count").unwrap(), Value::Number(9.into()) ); assert_eq!(vm.object_store().change_version(value).unwrap(), version); assert!(vm.del_attr(value, "count").is_err()); vm.set_attr(value, "count", Value::Number(10.into())) .unwrap(); // 用同名新类替换全局 Base,旧 value 的名义类型图仍保留最初的 Base 身份。 execute(&mut vm, globals, "class Base {}", ParseMode::Interactive); // 其他名义类型名称在声明环境中动态解析;Base 重定义后 Holder 的字段约束应按新身份检查。 // Other nominal names resolve dynamically in the declaring environment. assert!(matches!( try_execute(&mut vm, globals, "Holder(value)", ParseMode::Expression), Err(VmError::TypeMismatch { .. }) )); // 继承来的 count 保留其原声明字段元数据,环境同名类重定义不抹掉旧实例的数值约束。 // An inherited scalar field still belongs to its original declaration. vm.set_attr(value, "count", Value::Number(11.into())) .unwrap(); // 先清环境的全局拥有边,打断函数/类型与环境之间可能的引用环。 vm.dict_clear(globals).unwrap(); // 归还环境外部根,剩余图是否存活只由其他拥有根决定。 vm.release(globals).unwrap(); // 收集已无外部根的类型/函数/环境环;仍由实例或回调拥有的图必须保留。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] /// 由 Rust 测试框架调用:验证声明类自身的递归类型按原身份检查,即使全局同名类重定义;关闭检查允许临时写入 Nil。 fn checked_fields_keep_declaring_self_type_and_can_be_opted_out() { // 隔离本场景堆、类型配置与执行状态,最后检查没有遗留对象。 let mut vm = Vm::new().unwrap(); // 开启源码注解及字段检查,下面的类型错误才会按约束拒绝。 vm.set_source_type_checks(true).unwrap(); // 测试持环境初始根,源码函数和类型声明可在内部引用此环境。 let globals = vm.new_dict().unwrap(); // 引导得到 Object/Type 拥有根,注册环境后分别归还测试根。 let roots = vm.bootstrap_types().unwrap(); vm.dict_set( globals, Value::Text("Object".into()), Value::Object(roots.object), ) .unwrap(); vm.release(roots.object).unwrap(); vm.release(roots.type_type).unwrap(); // Link.next 是递归且不可空的字段;Seed 声明展示继承,后续由可信宿主直接构造旧 Link 实例。 execute( &mut vm, globals, r#" class Link { next: Link } class Seed: Link {} "#, ParseMode::Module, ); // A recursive nonnullable field can be initialized by the trusted host in two steps. // 从 globals 借用最初 Link 类型,后面同名重定义不应改变此类型的自引用身份。 let Value::Object(link) = vm .dict_get(globals, &Value::Text("Link".into())) .unwrap() .unwrap() else { panic!("class") }; // 宿主先分配空对象再附类型,用两步构造填入不可空的递归 next 字段。 let seed = vm.new_object().unwrap(); // 附上旧 Link 名义类型,此时先由可信宿主完成递归对象初始化。 vm.attach_type(seed, link).unwrap(); vm.set_attr(seed, "next", Value::Object(seed)).unwrap(); vm.dict_set(globals, Value::Text("old".into()), Value::Object(seed)) .unwrap(); // 新 Link 采用不同字段布局,只替换环境绑定;seed 及其 next 的原声明身份应不受影响。 execute( &mut vm, globals, "class Link { value: Number }", ParseMode::Interactive, ); vm.set_attr(seed, "next", Value::Object(seed)).unwrap(); // 旧 Link 的自引用字段不可空,即使当前全局 Link 已换成另一个类。 assert!(matches!( vm.set_attr(seed, "next", Value::Nil), Err(VmError::TypeMismatch { .. }) )); // 显式关闭检查,允许下一步向不可空字段写 Nil;这不会改变声明本身。 vm.set_source_type_checks(false).unwrap(); vm.set_attr(seed, "next", Value::Nil).unwrap(); // 恢复检查配置后写回旧 Link 自身实例,验证声明身份和约束没有被关闭操作修改。 vm.set_source_type_checks(true).unwrap(); vm.set_attr(seed, "next", Value::Object(seed)).unwrap(); // 归还种子外部根;环境清理后,自引用环仍需循环收集器处理。 vm.release(seed).unwrap(); // 先清环境的全局拥有边,打断函数/类型与环境之间可能的引用环。 vm.dict_clear(globals).unwrap(); // 归还环境外部根,剩余图是否存活只由其他拥有根决定。 vm.release(globals).unwrap(); // 收集已无外部根的类型/函数/环境环;仍由实例或回调拥有的图必须保留。 vm.collect_cycles(); assert_eq!(vm.object_count(), 0); }