723 lines
36 KiB
Rust
723 lines
36 KiB
Rust
//! Rust 测试框架驱动的源码→模板→代码对象→VM 集成验证;成功和错误路径都检查拥有根清理。
|
|
//! compile/module/function/cleanup 为本文件测试复用,编译器本身不执行源码,只有测试显式 load/execute/call。
|
|
use compiler::{CompileOptions, CompiledCode, NameBinding, analyze, compile_ast, compile_source};
|
|
use endor_core::{
|
|
EndorCodeObject, EndorFunctionObject, EndorNumber, ExecutionLimits, NativeFunction, ObjectRef,
|
|
Value, Vm, VmError,
|
|
};
|
|
use frontend::{DiagnosticKind, EndorReader, ParseMode, SourceUnit, parse_ast};
|
|
|
|
/// 测试共用的源码入口,按 mode 编译,不声明额外宿主名;返回未加载、独立于 VM 堆的模板。
|
|
fn compile(text: &str, mode: ParseMode) -> CompiledCode {
|
|
compile_source(
|
|
&EndorReader::from_text("<test>", text),
|
|
mode,
|
|
&CompileOptions::default(),
|
|
)
|
|
.unwrap()
|
|
}
|
|
|
|
/// 测试共用的模块加载入口:取得代码根、创建环境并执行声明,模块结果应为 Nil;返回两个拥有根供 cleanup。
|
|
fn module(vm: &mut Vm, compiled: &CompiledCode) -> (ObjectRef, ObjectRef) {
|
|
// 把模板显式装入当前 VM,取得需要归还的代码拥有根。
|
|
let code = compiled.load(vm).unwrap();
|
|
// 新建环境拥有根;函数声明和宿主值通过内部边持有,测试结束需清理环境。
|
|
let globals = vm.new_dict().unwrap();
|
|
// 执行加载的代码取得拥有返回值;模块为 Nil,表达式则为其实际结果。
|
|
let value = vm.execute(code, globals).unwrap();
|
|
assert_eq!(value, Value::Nil);
|
|
// 归还 execute/call 返回值拥有根;立即值也可以统一通过此入口处理。
|
|
vm.release_value(value).unwrap();
|
|
(code, globals)
|
|
}
|
|
|
|
/// 测试按名称从环境借用函数句柄;不 retain,调用者须保持 globals 或其他拥有图存活。
|
|
fn function(vm: &Vm, globals: ObjectRef, name: &str) -> ObjectRef {
|
|
// 环境键命中必须是函数对象;这里只借用,不新取得外部根。
|
|
let Some(Value::Object(function)) = vm.dict_get(globals, &Value::Text(name.into())).unwrap()
|
|
else {
|
|
panic!("missing function {name}")
|
|
};
|
|
function
|
|
}
|
|
|
|
/// 测试结束时归还代码根、清环境并归还环境根,再收集引用环;预期结果根已由调用者释放。
|
|
fn cleanup(vm: &mut Vm, code: ObjectRef, globals: ObjectRef) {
|
|
// 归还本轮代码外部根,函数或返回结果需要的内部图仍由各自引用持有。
|
|
vm.release(code).unwrap();
|
|
// 清除环境拥有边,打断声明函数/类型到环境的可能引用环。
|
|
vm.dict_clear(globals).unwrap();
|
|
// 归还环境初始根;之后只应留下测试明确保存的结果或回调根。
|
|
vm.release(globals).unwrap();
|
|
// 清理零外根的引用环,随后对象数应回到零。
|
|
vm.collect_cycles();
|
|
assert_eq!(vm.object_count(), 0);
|
|
}
|
|
|
|
#[test]
|
|
/// 由 Rust 测试框架调用:逐个加载示例模块并调用 main,验证前向调用、函数值遮蔽、Unicode 返回及精确十进制,并检查代码名和清理。
|
|
fn source_files_execute_through_module_and_function_code_objects() {
|
|
// 逐项使用独立代码路径核对指定源码与预期值,任一不符都使本测试失败。
|
|
for (text, expected) in [
|
|
(
|
|
include_str!("../examples/hello.endor"),
|
|
Value::Text("你好,Endor 🌱".into()),
|
|
),
|
|
(
|
|
include_str!("../examples/calls.endor"),
|
|
Value::Text("函数调用已接通".into()),
|
|
),
|
|
(
|
|
include_str!("../examples/shadowing.endor"),
|
|
Value::Text("局部名称优先".into()),
|
|
),
|
|
(
|
|
include_str!("../examples/number.endor"),
|
|
Value::Number(EndorNumber::decimal("-123456789012345678901234567890.00100").unwrap()),
|
|
),
|
|
] {
|
|
// 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。
|
|
let compiled = compile(text, ParseMode::Module);
|
|
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。
|
|
let (code, globals) = module(&mut vm, &compiled);
|
|
// 从环境借用 main,环境在调用完成前保持存活。
|
|
let main = function(&vm, globals, "main");
|
|
// 检查源码函数的代码元数据,证明 main 不是宿主替代回调。
|
|
let data = vm.payload::<EndorFunctionObject>(main, "function").unwrap();
|
|
assert_eq!(
|
|
vm.payload::<EndorCodeObject>(data.code(), "code")
|
|
.unwrap()
|
|
.code()
|
|
.name(),
|
|
"main"
|
|
);
|
|
// 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。
|
|
let result = vm.call(main, &[]).unwrap();
|
|
assert_eq!(result, expected);
|
|
// 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。
|
|
vm.release_value(result).unwrap();
|
|
cleanup(&mut vm, code, globals);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
/// 由 Rust 测试框架调用:表达式模式直接返回结果,验证文本、大整数、连续负号、前导零和负零均保持精确数值语义。
|
|
fn expression_mode_preserves_exact_literals_and_signs() {
|
|
// 逐项使用独立代码路径核对指定源码与预期值,任一不符都使本测试失败。
|
|
for (text, expected) in [
|
|
(
|
|
include_str!("../examples/literal.endor"),
|
|
Value::Text("表达式 → 字节码 → VM".into()),
|
|
),
|
|
(
|
|
"-99999999999999999999999999999999999999",
|
|
Value::Number(EndorNumber::integer("-99999999999999999999999999999999999999").unwrap()),
|
|
),
|
|
("--00042", Value::Number(42_i64.into())),
|
|
(
|
|
"-0.000",
|
|
Value::Number(EndorNumber::decimal("-0.000").unwrap()),
|
|
),
|
|
] {
|
|
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 表达式模板在当前 VM 单独装载,加载代码拥有根由本轮测试负责归还。
|
|
let code = compile(text, ParseMode::Expression).load(&mut vm).unwrap();
|
|
// 新建环境拥有根;函数声明和宿主值通过内部边持有,测试结束需清理环境。
|
|
let globals = vm.new_dict().unwrap();
|
|
// 执行加载的代码取得拥有返回值;模块为 Nil,表达式则为其实际结果。
|
|
let value = vm.execute(code, globals).unwrap();
|
|
assert_eq!(value, expected);
|
|
// 归还 execute/call 返回值拥有根;立即值也可以统一通过此入口处理。
|
|
vm.release_value(value).unwrap();
|
|
cleanup(&mut vm, code, globals);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
/// 由 Rust 测试框架调用:直接检查符号表的参数/局部槽、前向全局名绑定与空自由变量表,不加载 VM。
|
|
fn scope_analysis_resolves_local_shadowing_and_forward_globals() {
|
|
// 当前用例的源码或带来源快照,下面按指定模式解析/编译。
|
|
let source = "function main(target: Callable): String { let copy = target return later(copy) } function later(value: String): String { return value }";
|
|
// 先解析 AST,再单独调用名称分析,隔离解析与分析阶段的结果。
|
|
let root = parse_ast(source, ParseMode::Module).unwrap();
|
|
// 不创建 VM,直接检查 analyze 输出的稳定参数/局部与全局索引。
|
|
let symbols = analyze(&root, &CompileOptions::default()).unwrap();
|
|
assert_eq!(symbols.functions[0].layout.parameters, ["target"]);
|
|
assert_eq!(symbols.functions[0].layout.locals, ["target", "copy"]);
|
|
assert_eq!(
|
|
symbols.functions[0]
|
|
.references
|
|
.iter()
|
|
.map(|reference| reference.binding)
|
|
.collect::<Vec<_>>(),
|
|
[
|
|
NameBinding::Local(0),
|
|
NameBinding::Global(0),
|
|
NameBinding::Local(1)
|
|
]
|
|
);
|
|
// 当前源码函数不捕获外层局部,所有代码布局的自由变量表都应为空。
|
|
assert!(
|
|
symbols
|
|
.functions
|
|
.iter()
|
|
.all(|scope| scope.layout.free_variables.is_empty())
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
/// 由 Rust 测试框架调用:参数/局部/顶层重复、未声明及未初始化读取都在编译时拒绝,并检查 Unicode 与 CRLF 的诊断行列。
|
|
fn semantic_errors_prevent_loading_and_keep_unicode_locations() {
|
|
// 每例给出非法源码及应包含的诊断片段,确保失败原因与目标规则对应。
|
|
for (text, fragment) in [
|
|
(
|
|
"function f(): String { return missing }",
|
|
"undefined name: missing",
|
|
),
|
|
("function f(x: T, x: T): T { return x }", "declaration: x"),
|
|
(
|
|
"function f(x: T): T { let x = 1 return x }",
|
|
"declaration: x",
|
|
),
|
|
(
|
|
"function f(): T { let x = 1 let x = 2 return x }",
|
|
"declaration: x",
|
|
),
|
|
(
|
|
"function f(): T { return 1 } function f(): T { return 2 }",
|
|
"declaration: f",
|
|
),
|
|
(
|
|
"function f(): T { let x = x return x }",
|
|
"before initialization: x",
|
|
),
|
|
(
|
|
"function f(): T { let x = later let later = 1 return x }",
|
|
"before initialization: later",
|
|
),
|
|
(
|
|
"function later(): T { return 1 } function f(): T { let x = later let later = 1 return x }",
|
|
"before initialization: later",
|
|
),
|
|
] {
|
|
// 保留来源名和原始源码,编译诊断应带有正确位置及错误说明。
|
|
let unit = EndorReader::from_text("<invalid>", text);
|
|
// 预期编译失败,之后检查诊断阶段或位置,不能走加载执行路径。
|
|
let error =
|
|
compile_source(&unit, ParseMode::Module, &CompileOptions::default()).unwrap_err();
|
|
assert_eq!(error.diagnostic.kind, DiagnosticKind::Semantic);
|
|
assert!(error.to_string().contains(fragment), "{error}");
|
|
}
|
|
// 保留来源名和原始源码,编译诊断应带有正确位置及错误说明。
|
|
let unit = EndorReader::from_text("<日本語>", "function 表示(): T {\r\n return 未定義\r\n}");
|
|
// 预期编译失败,之后检查诊断阶段或位置,不能走加载执行路径。
|
|
let error = compile_source(&unit, ParseMode::Module, &CompileOptions::default()).unwrap_err();
|
|
assert_eq!((error.location.line, error.location.column), (2, 10));
|
|
assert!(error.to_string().starts_with("<日本語>:2:10:"));
|
|
}
|
|
|
|
/// 宿主 NativeFunction 调用的恒等回调;参数为借用值,clone 不增对象根,VM 返回协议负责保留结果。
|
|
fn identity(arguments: &[Value]) -> Result<Value, VmError> {
|
|
// 恒等回调只接受一个实参,多参/缺参明确报错而非越界取参数。
|
|
if arguments.len() != 1 {
|
|
return Err(VmError::InvalidArgument("identity needs one argument"));
|
|
}
|
|
Ok(arguments[0].clone())
|
|
}
|
|
|
|
#[test]
|
|
/// 由 Rust 测试框架调用:验证宿主声明、属性原生调用和拥有对象返回;清空环境后仅结果根保活对象,释放结果后句柄失效。
|
|
fn host_globals_member_calls_and_object_results_keep_owned_roots() {
|
|
// 编译期仅声明可用宿主名;真正的绑定值仍由 globals 提供。
|
|
let options = CompileOptions {
|
|
globals: vec!["service".into(), "value".into()],
|
|
};
|
|
// 保留来源名和原始源码,编译诊断应带有正确位置及错误说明。
|
|
let unit = EndorReader::from_text("<host>", "service.echo(value)");
|
|
// 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。
|
|
let compiled = compile_source(&unit, ParseMode::Expression, &options).unwrap();
|
|
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 分配恒等原生回调作为 service.echo,属性内部边随后保留它。
|
|
let native = vm.allocate(NativeFunction::new("echo", identity)).unwrap();
|
|
// 普通宿主对象承载 echo 属性,用于验证成员查找与原生调用链。
|
|
let service = vm.new_object().unwrap();
|
|
vm.set_attr(service, "echo", Value::Object(native)).unwrap();
|
|
// service.echo 已取得内部边,测试不再额外持原生函数根。
|
|
vm.release(native).unwrap();
|
|
// 作为实参及最终返回的托管对象,后续归还原根以检查 VM 返回协议。
|
|
let object = vm.new_object().unwrap();
|
|
// 新建环境拥有根;函数声明和宿主值通过内部边持有,测试结束需清理环境。
|
|
let globals = vm.new_dict().unwrap();
|
|
vm.dict_set(
|
|
globals,
|
|
Value::Text("service".into()),
|
|
Value::Object(service),
|
|
)
|
|
.unwrap();
|
|
vm.dict_set(globals, Value::Text("value".into()), Value::Object(object))
|
|
.unwrap();
|
|
// 环境已持 service,归还宿主初始根。
|
|
vm.release(service).unwrap();
|
|
// 环境已持实参对象,归还初始根,之后返回结果必须获得自己的根。
|
|
vm.release(object).unwrap();
|
|
// 把模板显式装入当前 VM,取得需要归还的代码拥有根。
|
|
let code = compiled.load(&mut vm).unwrap();
|
|
// 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。
|
|
let result = vm.execute(code, globals).unwrap();
|
|
assert_eq!(result, Value::Object(object));
|
|
// 清除环境拥有边,打断声明函数/类型到环境的可能引用环。
|
|
vm.dict_clear(globals).unwrap();
|
|
assert!(vm.object(object).is_ok());
|
|
// 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。
|
|
vm.release_value(result).unwrap();
|
|
assert_eq!(vm.object(object).unwrap_err(), VmError::InvalidHandle);
|
|
cleanup(&mut vm, code, globals);
|
|
}
|
|
|
|
#[test]
|
|
/// 由 Rust 测试框架调用:同一模板装入两个独立 VM,跨堆句柄被拒绝;重复调用复用已加载代码,不增加堆对象。
|
|
fn code_templates_load_independently_and_functions_share_loaded_code() {
|
|
// 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。
|
|
let compiled = compile(include_str!("../examples/calls.endor"), ParseMode::Module);
|
|
// 第一个 VM 单独加载模板,句柄不能被另一个堆接受。
|
|
let mut left = Vm::new().unwrap();
|
|
// 第二个 VM 加载同一模板,但得到自身堆专属代码及环境。
|
|
let mut right = Vm::new().unwrap();
|
|
// 左堆建立独立模块图,后续重复调用仅访问这份代码。
|
|
let (left_code, left_globals) = module(&mut left, &compiled);
|
|
// 右堆建立另一份图,用来检查 ForeignHandle 拒绝跨 VM 引用。
|
|
let (right_code, right_globals) = module(&mut right, &compiled);
|
|
assert_eq!(left.object(right_code).unwrap_err(), VmError::ForeignHandle);
|
|
// 记录多次调用前堆大小,调用结束不应重建源码函数代码或残留临时图。
|
|
let before = left.object_count();
|
|
// 从环境借用 main,环境在调用完成前保持存活。
|
|
let main = function(&left, left_globals, "main");
|
|
// 重复运行同一函数三次,每次归还结果,以对象数验证代码复用。
|
|
for _ in 0..3 {
|
|
// 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。
|
|
let result = left.call(main, &[]).unwrap();
|
|
left.release_value(result).unwrap();
|
|
}
|
|
assert_eq!(left.object_count(), before);
|
|
cleanup(&mut left, left_code, left_globals);
|
|
cleanup(&mut right, right_code, right_globals);
|
|
}
|
|
|
|
#[test]
|
|
/// 由 Rust 测试框架调用:缺参和递归帧上限错误释放执行临时图,同时保留源码位置或限制深度对应的回溯。
|
|
fn argument_and_recursion_failures_unwind_vm_frames_and_keep_traceback() {
|
|
// 当前用例的源码或带来源快照,下面按指定模式解析/编译。
|
|
let source =
|
|
"\nfunction main(): T { return echo() }\nfunction echo(value: T): T { return value }";
|
|
// 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。
|
|
let compiled = compile(source, ParseMode::Module);
|
|
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。
|
|
let (code, globals) = module(&mut vm, &compiled);
|
|
// 记录错误调用前对象数,参数/返回检查或帧清理后不能增加存活对象。
|
|
let baseline = vm.object_count();
|
|
// 从环境借用 main,环境在调用完成前保持存活。
|
|
let main = function(&vm, globals, "main");
|
|
// main 调用 echo 时漏传 value,错误出在被调函数预检,不能残留执行帧或临时根。
|
|
assert!(matches!(
|
|
vm.call(main, &[]),
|
|
Err(VmError::ArgumentCount { provided: 0, .. })
|
|
));
|
|
assert_eq!(vm.object_count(), baseline);
|
|
assert_eq!(vm.last_traceback().last().unwrap().first_line, 2);
|
|
cleanup(&mut vm, code, globals);
|
|
|
|
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。
|
|
let (code, globals) = module(
|
|
&mut vm,
|
|
&compile("function main(): T { return main() }", ParseMode::Module),
|
|
);
|
|
// 从环境借用 main,环境在调用完成前保持存活。
|
|
let main = function(&vm, globals, "main");
|
|
assert_eq!(
|
|
vm.call_with_limits(
|
|
main,
|
|
&[],
|
|
ExecutionLimits {
|
|
max_frames: 4,
|
|
max_instructions: 100
|
|
}
|
|
),
|
|
Err(VmError::ExecutionLimit("frames"))
|
|
);
|
|
assert_eq!(vm.last_traceback().len(), 4);
|
|
cleanup(&mut vm, code, globals);
|
|
}
|
|
|
|
#[test]
|
|
/// 由 Rust 测试框架调用:编译选项只声明合法名字,不注入 VM 值;空环境执行仍报告 NameNotFound。
|
|
fn declared_host_names_are_checked_again_at_runtime() {
|
|
// 当前用例的源码或带来源快照,下面按指定模式解析/编译。
|
|
let source = EndorReader::from_text("<host>", "missing");
|
|
// 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。
|
|
let compiled = compile_source(
|
|
&source,
|
|
ParseMode::Expression,
|
|
&CompileOptions {
|
|
globals: vec!["missing".into()],
|
|
},
|
|
)
|
|
.unwrap();
|
|
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 把模板显式装入当前 VM,取得需要归还的代码拥有根。
|
|
let code = compiled.load(&mut vm).unwrap();
|
|
// 新建环境拥有根;函数声明和宿主值通过内部边持有,测试结束需清理环境。
|
|
let globals = vm.new_dict().unwrap();
|
|
assert_eq!(
|
|
vm.execute(code, globals),
|
|
Err(VmError::NameNotFound("missing".into()))
|
|
);
|
|
cleanup(&mut vm, code, globals);
|
|
}
|
|
|
|
#[test]
|
|
/// 由 Rust 测试框架调用:两次单语句交互共享宿主环境,首次绑定返回 Nil,后续读取返回此前保存值。
|
|
fn interactive_submissions_can_share_a_host_owned_namespace() {
|
|
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 新建环境拥有根;函数声明和宿主值通过内部边持有,测试结束需清理环境。
|
|
let globals = vm.new_dict().unwrap();
|
|
// 两次独立交互提交共享同一个宿主环境;名字声明随提交需求分别提供。
|
|
for (text, options, expected) in [
|
|
(
|
|
"let value = \"保留\"",
|
|
CompileOptions::default(),
|
|
Value::Nil,
|
|
),
|
|
(
|
|
"value",
|
|
CompileOptions {
|
|
globals: vec!["value".into()],
|
|
},
|
|
Value::Text("保留".into()),
|
|
),
|
|
] {
|
|
// 当前用例的源码或带来源快照,下面按指定模式解析/编译。
|
|
let source = EndorReader::from_text("<submission>", text);
|
|
// 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。
|
|
let compiled = compile_source(&source, ParseMode::Interactive, &options).unwrap();
|
|
// 把模板显式装入当前 VM,取得需要归还的代码拥有根。
|
|
let code = compiled.load(&mut vm).unwrap();
|
|
// 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。
|
|
let result = vm.execute(code, globals).unwrap();
|
|
assert_eq!(result, expected);
|
|
// 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。
|
|
vm.release_value(result).unwrap();
|
|
// 归还本轮代码外部根,函数或返回结果需要的内部图仍由各自引用持有。
|
|
vm.release(code).unwrap();
|
|
}
|
|
// 清除环境拥有边,打断声明函数/类型到环境的可能引用环。
|
|
vm.dict_clear(globals).unwrap();
|
|
// 归还环境初始根;之后只应留下测试明确保存的结果或回调根。
|
|
vm.release(globals).unwrap();
|
|
assert_eq!(vm.object_count(), 0);
|
|
}
|
|
|
|
#[test]
|
|
/// 由 Rust 测试框架调用:构造 270 个局部与常量,迫使索引使用扩展操作数,执行返回最后一个值验证编码解码贯通。
|
|
fn extended_operands_work_for_large_local_and_constant_tables() {
|
|
// 动态拼接大量独立局部声明,测试超过单字节的索引编码。
|
|
let mut source = String::from("function main(): Number { ");
|
|
// 超过 255 的局部/常量索引需要 EXTENDED_ARG,返回值来自最后一个槽。
|
|
for index in 0..270 {
|
|
source.push_str(&format!("let value{index} = {index} "));
|
|
}
|
|
source.push_str("return value269 }");
|
|
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。
|
|
let (code, globals) = module(&mut vm, &compile(&source, ParseMode::Module));
|
|
// 从环境借用 main,环境在调用完成前保持存活。
|
|
let main = function(&vm, globals, "main");
|
|
// 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。
|
|
let result = vm.call(main, &[]).unwrap();
|
|
assert_eq!(result, Value::Number(269_i64.into()));
|
|
// 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。
|
|
vm.release_value(result).unwrap();
|
|
cleanup(&mut vm, code, globals);
|
|
}
|
|
|
|
#[test]
|
|
/// 由 Rust 测试框架调用:前端可解析的 Symbol 暂无 VM 值,在编译生成阶段明确失败。
|
|
fn unsupported_execution_features_are_compile_errors_instead_of_fake_results() {
|
|
// 当前用例的源码或带来源快照,下面按指定模式解析/编译。
|
|
let source = EndorReader::from_text("<unsupported>", ".title");
|
|
// 预期编译失败,之后检查诊断阶段或位置,不能走加载执行路径。
|
|
let error =
|
|
compile_source(&source, ParseMode::Expression, &CompileOptions::default()).unwrap_err();
|
|
assert_eq!(error.diagnostic.kind, DiagnosticKind::Compilation);
|
|
}
|
|
|
|
#[test]
|
|
/// 由 Rust 测试框架调用:破坏手工 AST 的 UTF-8 边界,验证直接 AST 编译入口先拒绝无效源跨度。
|
|
fn compiling_ast_rejects_invalid_source_ranges() {
|
|
// 保留含三字节字符的原始快照,用于故意制造 UTF-8 非边界跨度。
|
|
let source: SourceUnit = EndorReader::from_text("<source>", "\"日\"");
|
|
// 解析合法表达式 AST,再手工修改跨度,仅测试 AST 入口验证。
|
|
let mut root = parse_ast(source.text(), ParseMode::Expression).unwrap();
|
|
// 将 end 改到日字的多字节编码内部,语法树内容不变但位置应非法。
|
|
if let frontend::ast::AstRoot::Expression(expression) = &mut root {
|
|
expression.span.end = 2; // Splits the three-byte character.
|
|
}
|
|
assert!(compile_ast(&root, &source, &CompileOptions::default()).is_err());
|
|
}
|
|
|
|
#[test]
|
|
/// 由 Rust 测试框架调用:交互裸赋值创建再替换全局,下一次编译从现有环境取得宿主名字,最终读取最新值。
|
|
fn bare_assignments_create_and_replace_real_interactive_globals() {
|
|
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 新建环境拥有根;函数声明和宿主值通过内部边持有,测试结束需清理环境。
|
|
let globals = vm.new_dict().unwrap();
|
|
// 同一环境连续执行创建、替换和读取,后面的编译应看到前面登记的名字。
|
|
for text in ["value = \"first\"", "value = \"second\"", "value"] {
|
|
// 编译期仅声明可用宿主名;真正的绑定值仍由 globals 提供。
|
|
let options = CompileOptions {
|
|
globals: vm
|
|
.dict_items(globals)
|
|
.unwrap()
|
|
.iter()
|
|
// 只允许文本环境键成为源码可用名称,不把非文本键当标识符。
|
|
.filter_map(|(key, _)| match key {
|
|
Value::Text(name) => Some(name.clone()),
|
|
_ => None,
|
|
})
|
|
.collect(),
|
|
};
|
|
// 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。
|
|
let compiled = compile_source(
|
|
&EndorReader::from_text("<session>", text),
|
|
ParseMode::Interactive,
|
|
&options,
|
|
)
|
|
.unwrap();
|
|
// 把模板显式装入当前 VM,取得需要归还的代码拥有根。
|
|
let code = compiled.load(&mut vm).unwrap();
|
|
// 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。
|
|
let result = vm.execute(code, globals).unwrap();
|
|
// 只有最后的表达式提交返回文本;赋值提交只产生 Nil。
|
|
if text == "value" {
|
|
assert_eq!(result, Value::Text("second".into()));
|
|
}
|
|
// 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。
|
|
vm.release_value(result).unwrap();
|
|
// 归还本轮代码外部根,函数或返回结果需要的内部图仍由各自引用持有。
|
|
vm.release(code).unwrap();
|
|
}
|
|
assert_eq!(
|
|
vm.dict_get(globals, &Value::Text("value".into())).unwrap(),
|
|
Some(Value::Text("second".into()))
|
|
);
|
|
// 清除环境拥有边,打断声明函数/类型到环境的可能引用环。
|
|
vm.dict_clear(globals).unwrap();
|
|
// 归还环境初始根;之后只应留下测试明确保存的结果或回调根。
|
|
vm.release(globals).unwrap();
|
|
// 清理零外根的引用环,随后对象数应回到零。
|
|
vm.collect_cycles();
|
|
assert_eq!(vm.object_count(), 0);
|
|
}
|
|
|
|
#[test]
|
|
/// 由 Rust 测试框架调用:赋值复用参数及同名局部槽,执行验证更新结果;自读未初始化与重复 let 仍编译失败。
|
|
fn assignments_update_parameters_and_allocate_one_local_slot_per_target() {
|
|
// 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。
|
|
let compiled = compile(
|
|
"function main(value: String): String { copy = value copy = \"updated\" value = copy return value }",
|
|
ParseMode::Module,
|
|
);
|
|
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。
|
|
let (code, globals) = module(&mut vm, &compiled);
|
|
// 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。
|
|
let result = vm
|
|
.call(
|
|
function(&vm, globals, "main"),
|
|
&[Value::Text("original".into())],
|
|
)
|
|
.unwrap();
|
|
assert_eq!(result, Value::Text("updated".into()));
|
|
// 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。
|
|
vm.release_value(result).unwrap();
|
|
cleanup(&mut vm, code, globals);
|
|
assert!(
|
|
compile_source(
|
|
&EndorReader::from_text("<uninitialized>", "function f(): T { x = x return x }"),
|
|
ParseMode::Module,
|
|
&CompileOptions::default()
|
|
)
|
|
.is_err()
|
|
);
|
|
assert!(
|
|
compile_source(
|
|
&EndorReader::from_text(
|
|
"<duplicate>",
|
|
"function f(): T { let x = 1 let x = 2 return x }"
|
|
),
|
|
ParseMode::Module,
|
|
&CompileOptions::default()
|
|
)
|
|
.is_err()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
/// 由 Rust 测试框架调用:检查模板保存类型签名,加载后开启运行时检查分别拒绝错误参数与错误返回,错误不泄漏对象。
|
|
fn source_signatures_survive_template_load_and_enable_runtime_checks() {
|
|
// 编译得到模板,尚未分配 VM 代码对象;具体模式及宿主名称由当前用例提供。
|
|
let compiled = compile(
|
|
"function identity(value: Number): Number { return value }\nfunction bad(): Number { return \"wrong\" }",
|
|
ParseMode::Module,
|
|
);
|
|
// 检查首个子函数模板的源码签名,尚未加载时也必须保留注解。
|
|
let signature = compiled
|
|
.children()
|
|
.next()
|
|
.unwrap()
|
|
.source_signature()
|
|
.unwrap();
|
|
assert_eq!(signature.parameters, ["Number"]);
|
|
assert_eq!(signature.return_type, "Number");
|
|
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 启用运行时源码签名检查,错误参数在函数体前拒绝,错误返回在出口拒绝。
|
|
vm.set_source_type_checks(true).unwrap();
|
|
// 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。
|
|
let (code, globals) = module(&mut vm, &compiled);
|
|
// 借用已加载的 Number 恒等函数,分别尝试合法数值和非法文本。
|
|
let identity = function(&vm, globals, "identity");
|
|
assert_eq!(
|
|
vm.call(identity, &[Value::Number(7.into())]).unwrap(),
|
|
Value::Number(7.into())
|
|
);
|
|
// 开启检查后,String 实参不能通过 Number 参数约束。
|
|
assert!(matches!(
|
|
vm.call(identity, &[Value::Text("wrong".into())]),
|
|
Err(VmError::TypeMismatch { .. })
|
|
));
|
|
// 借用声明返回 Number 但实际返回文本的函数,专门验证返回类型约束。
|
|
let bad = function(&vm, globals, "bad");
|
|
// 记录错误调用前对象数,参数/返回检查或帧清理后不能增加存活对象。
|
|
let baseline = vm.object_count();
|
|
// bad 的函数体能执行,但出口文本不符合声明的 Number 返回类型。
|
|
assert!(matches!(
|
|
vm.call(bad, &[]),
|
|
Err(VmError::TypeMismatch { .. })
|
|
));
|
|
assert_eq!(vm.object_count(), baseline);
|
|
cleanup(&mut vm, code, globals);
|
|
}
|
|
|
|
#[test]
|
|
/// 由 Rust 测试框架调用:运行 while/if 与加减得到 -100,再检查精确小数、大整数、有理数和非字面量取负。
|
|
fn source_control_flow_and_exact_arithmetic_execute_real_bytecode() {
|
|
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 先累加 0..9 再减去 10..19,循环与分支结果应是精确整数 -100。
|
|
let text = "function main(): Number { i=0 total=0 while i<20 { if i<10 {total=total+i} else {total=total-i} i=i+1 } return total }";
|
|
// 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。
|
|
let (code, globals) = module(&mut vm, &compile(text, ParseMode::Module));
|
|
// 从环境借用 main,环境在调用完成前保持存活。
|
|
let main = function(&vm, globals, "main");
|
|
// 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。
|
|
let result = vm.call(main, &[]).unwrap();
|
|
assert_eq!(result, Value::Number((-100_i64).into()));
|
|
// 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。
|
|
vm.release_value(result).unwrap();
|
|
cleanup(&mut vm, code, globals);
|
|
// 逐项使用独立代码路径核对指定源码与预期值,任一不符都使本测试失败。
|
|
for (text, expected) in [
|
|
("0.1 + 0.2 == 0.3", Value::Bool(true)),
|
|
(
|
|
"100000000000000000000 + 1 > 100000000000000000000",
|
|
Value::Bool(true),
|
|
),
|
|
("1 / 3 + 1 / 3 + 1 / 3", Value::Number(1_i64.into())),
|
|
("-(2 + 3)", Value::Number((-5_i64).into())),
|
|
] {
|
|
// 表达式模板在当前 VM 单独装载,加载代码拥有根由本轮测试负责归还。
|
|
let code = compile(text, ParseMode::Expression).load(&mut vm).unwrap();
|
|
// 新建环境拥有根;函数声明和宿主值通过内部边持有,测试结束需清理环境。
|
|
let globals = vm.new_dict().unwrap();
|
|
// 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。
|
|
let result = vm.execute(code, globals).unwrap();
|
|
assert_eq!(result, expected);
|
|
// 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。
|
|
vm.release_value(result).unwrap();
|
|
// 归还本轮代码外部根,函数或返回结果需要的内部图仍由各自引用持有。
|
|
vm.release(code).unwrap();
|
|
// 归还环境初始根;之后只应留下测试明确保存的结果或回调根。
|
|
vm.release(globals).unwrap();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
/// 由 Rust 测试框架调用:长循环体迫使相对跳转扩展,单侧初始化和零次循环后的读取编译失败,合法无限循环受指令总预算限制。
|
|
fn control_flow_definite_initialization_large_jumps_and_loop_budgets() {
|
|
// 拼接较长 if/while 体,迫使条件出口和回边跨越扩展编码距离。
|
|
let mut body = String::from("function main(): Number { i=0 while i<2 { if i==0 {");
|
|
// 重复运行同一函数三次,每次归还结果,以对象数验证代码复用。
|
|
for _ in 0..300 {
|
|
body.push_str("i=i+0 ");
|
|
}
|
|
body.push_str("} i=i+1 } return i }");
|
|
// 为当前场景或案例新建独立堆,避免其他执行状态掩盖错误与清理问题。
|
|
let mut vm = Vm::new().unwrap();
|
|
// 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。
|
|
let (code, globals) = module(&mut vm, &compile(&body, ParseMode::Module));
|
|
// 取得 VM 调用返回的拥有值,后续比较结果并归还它持有的对象根。
|
|
let result = vm.call(function(&vm, globals, "main"), &[]).unwrap();
|
|
assert_eq!(result, Value::Number(2_i64.into()));
|
|
// 归还独立结果根,不让返回对象掩盖环境/代码清理后的保活检查。
|
|
vm.release_value(result).unwrap();
|
|
cleanup(&mut vm, code, globals);
|
|
// 两种未确定初始化用例:if 只有一侧赋值,以及 while 可能零次执行。
|
|
for text in [
|
|
"function f(): Number { if 1 {x=2} return x }",
|
|
"function f(): Number {while 0 {x=2} return x}",
|
|
] {
|
|
assert!(
|
|
compile_source(
|
|
&EndorReader::from_text("<test>", text),
|
|
ParseMode::Module,
|
|
&CompileOptions::default()
|
|
)
|
|
.is_err()
|
|
);
|
|
}
|
|
// 加载并执行模块声明,保存两个拥有根,之后统一交 cleanup。
|
|
let (code, globals) = module(
|
|
&mut vm,
|
|
&compile(
|
|
"function main(): Unit { while 1 { value=2 } }",
|
|
ParseMode::Module,
|
|
),
|
|
);
|
|
// 无限循环没有返回出口,必须由累计指令预算终止,之后 cleanup 验证错误清理。
|
|
assert!(matches!(
|
|
vm.call_with_limits(
|
|
function(&vm, globals, "main"),
|
|
&[],
|
|
ExecutionLimits {
|
|
max_frames: 10,
|
|
max_instructions: 100
|
|
}
|
|
),
|
|
Err(VmError::ExecutionLimit("instructions"))
|
|
));
|
|
cleanup(&mut vm, code, globals);
|
|
}
|