Files
OxideJS-New/crates/oxide_vm/tests/string_rope_tests.rs
T

170 lines
6.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 二元 ConsString(rope)语义回归测试。
//!
//! 覆盖:`+`/`+=` 拼接产物(rope 节点)在各消费点(`==`/`.length`/string 方法/
//! for-of/属性键/模板/JSON/展开)的惰性扁平化正确性、数字/对象叶子混合、CONCAT_N
//! 产物作 rope 叶子、低 GC 阈值下跨执行期字符串回收的存活与 mark 传播。
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_kernel::kernel::{KernelConfig, KernelCore};
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
fn eval(vm: &mut Vm, source: &str) -> Result<JsValue, String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {e}"))?;
vm.run(&Arc::new(module))
}
fn eval_str(source: &str) -> String {
let mut vm = Vm::new();
let val = eval(&mut vm, source).expect("eval ok");
fmt(&vm, val)
}
fn fmt(vm: &Vm, val: JsValue) -> String {
if val.is_string() {
vm.lookup_str(val).unwrap_or_default()
} else if val.is_bigint() {
format!("{}", vm.bigint_value(val))
} else {
format!("{val}")
}
}
/// 低 GC 阈值 VM:水位=1 使指令边界检查几乎每指令触发,压 rope 的 mark 传播与
/// 跨回收存活路径。
fn vm_with_tiny_threshold() -> Vm {
let mut cfg = KernelConfig::minimal();
cfg.set_session_gc_threshold(1);
let core = KernelCore::new(cfg);
Vm::with_kernel_core(core)
}
#[test]
fn rope_binary_concat_content_and_length() {
// 二元 `+` 产物为 Cons 节点:内容与长度经惰性扁平化与缓存 O(1) 读取。
assert_eq!(eval_str("\"ab\" + \"cd\""), "abcd");
assert_eq!(eval_str("(\"ab\" + \"cd\").length"), "4");
assert_eq!(eval_str("(\"ab\" + \"cd\" + \"ef\").length"), "6");
assert_eq!(eval_str("(\"ab\" + \"cd\").charCodeAt(1)"), "98");
}
#[test]
fn rope_equality_with_literal_and_rope() {
// `==`/`===` 走 string_value_eq 内容比较:rope 与同内容字面量/另一 rope 相等,
// 与不同内容不等。
assert_eq!(eval_str("(\"ab\" + \"cd\") === \"abcd\""), "true");
assert_eq!(eval_str("(\"ab\" + \"cd\") == \"abcd\""), "true");
assert_eq!(eval_str("(\"ab\" + \"cd\") === (\"a\" + \"bcd\")"), "true");
assert_eq!(eval_str("(\"ab\" + \"cd\") === \"abce\""), "false");
}
#[test]
fn rope_compound_loop_builds_correctly() {
// string_build 主模式:`s += j` 循环 50 次(数字叶子 + Cons 链接),
// 末次 `.length` 扁平化结果正确(0..49 拼接:10 个单字符 + 40 个双字符 = 90)。
assert_eq!(
eval_str(
"var s = ''; for (var j = 0; j < 50; j++) { s += j } \
s + '|' + s.length"
),
"012345678910111213141516171819202122232425262728293031323334353637383940414243444546474849|90"
);
}
#[test]
fn rope_mixed_operand_leaf_conversion() {
// 非字符串操作数先转叶子再链接:数字/布尔/null/undefined/对象与字符串混拼。
assert_eq!(eval_str("var s = 'a'; s += 1; s += 2.5; s += true; s"), "a12.5true");
assert_eq!(eval_str("'x' + null + undefined"), "xnullundefined");
assert_eq!(eval_str("var s = 'a'; s += { toString: function () { return 'obj' } }; s"), "aobj");
}
#[test]
fn rope_concat_n_rhs_becomes_leaf() {
// `s += a+b+c`:rhs 摊平为 CONCAT_N(急切 Flat 产物),外层 COMPOUND_ADD 把
// Flat 产物链接成 Cons 子节点——两层机制叠加内容正确。
assert_eq!(eval_str("var s = ''; s += 'a' + 'b' + 'c'; s + s"), "abcabc");
assert_eq!(eval_str("var s = 'z'; for (var i = 0; i < 5; i++) { s += 'x' + 'y' } s"), "zxyxyxyxyxy");
}
#[test]
fn rope_as_property_key() {
// rope 作属性键:ToPropertyKey → flat 桥接 intern,键内容一致。
assert_eq!(eval_str("var s = 'a' + 'b'; var o = {}; o[s] = 42; o['ab']"), "42");
assert_eq!(eval_str("var s = '5'; var o = {}; o[s] = 7; o[5]"), "7");
}
#[test]
fn rope_for_of_iterates_chars() {
// for-of 字符串迭代 rope:首次访问扁平化后游标推进,逐字符正确。
assert_eq!(eval_str("var s = 'ab' + 'cd'; var out = ''; for (var c of s) { out += c } out"), "abcd");
assert_eq!(eval_str("var s = 'x'; s += 'y'; s += 'z'; [...s].join('-')"), "x-y-z");
}
#[test]
fn rope_consumed_by_string_methods() {
// string 方法族(this_string 借用 + 只读扫描)消费 rope 时惰性扁平化。
assert_eq!(eval_str("('hello' + 'world').indexOf('wo')"), "5");
assert_eq!(eval_str("('hello' + 'world').substring(2, 7)"), "llowo");
assert_eq!(eval_str("('ab' + 'cd').repeat(2)"), "abcdabcd");
assert_eq!(eval_str("('aB' + 'cD').toUpperCase()"), "ABCD");
assert_eq!(eval_str("('a,b' + ',c').split(',').length"), "3");
}
#[test]
fn rope_in_template_and_json() {
// 模板串与 JSON.stringify 消费 rope 结果一致。
assert_eq!(eval_str("var s = 'a' + 'b'; `${s}-x`"), "ab-x");
assert_eq!(eval_str("var s = 'x' + 'y'; JSON.stringify([s])"), "[\"xy\"]");
}
#[test]
fn rope_numeric_coercion_of_content() {
// rope 文本参与数值转换:ToNumber 走 string_data 扁平化文本。
assert_eq!(eval_str("('1' + '0') * 2"), "20");
assert_eq!(eval_str("Number('4' + '2')"), "42");
}
#[test]
fn rope_survives_runtime_string_gc() {
// 低阈值下指令边界检查几乎每指令触发:循环拼接全程(链接结果写寄存器先于
// 下次检查)不被误回收;大量中间垃圾触发多轮回收后 rope 仍正确消费。
let mut vm = vm_with_tiny_threshold();
let val = eval(
&mut vm,
"var s = 'a'; for (var i = 0; i < 300; i++) { s += 'b' } \
var garbage = ''; for (var g = 0; g < 500; g++) { garbage += 'x' } \
s.length + '|' + s.charAt(150)",
)
.expect("eval ok");
assert_eq!(fmt(&vm, val), "301|b");
}
#[test]
fn rope_in_object_holder_survives_gc() {
// rope 挂在全局对象属性上:mark 经对象字符串边传播 rope 闭包,子节点与
// 产物随父存活(跨多轮低阈值回收)。
let mut vm = vm_with_tiny_threshold();
let val = eval(
&mut vm,
"var o = {}; o.s = 'x'; for (var i = 0; i < 200; i++) { o.s += 'y' } \
var t = ''; for (var g = 0; g < 300; g++) { t += 'z' } \
o.s.length + '|' + o.s.charAt(200) + '|' + t.length",
)
.expect("eval ok");
assert_eq!(fmt(&vm, val), "201|y|300");
}
#[test]
fn rope_dead_after_scope_released() {
// rope 离开作用域成为垃圾:低阈值下被后续分配回收,不影响新值。
let mut vm = vm_with_tiny_threshold();
eval(&mut vm, "var t = ''; for (var i = 0; i < 200; i++) { t += 'z' }").expect("eval ok");
let val = eval(&mut vm, "'fresh' + 'value'").expect("eval ok");
assert_eq!(fmt(&vm, val), "freshvalue");
}