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

296 lines
8.9 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.
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
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 to_str(vm: &Vm, val: JsValue) -> String {
vm.lookup_str(val).unwrap_or_default()
}
#[test]
fn regexp_survives_gc_and_still_matches() {
// 回归:正则存入全局对象触发晋升/GC 搬移后,native_fn 槽的已编译 Box
// 必须深拷贝到新对象(而非共享指针),否则 epoch 释放与 teardown 双重释放。
let mut vm = Vm::new();
let result = eval(&mut vm, "var r = /ab+c/g; globalThis.r = r; r.test('xxabbcx')").unwrap();
assert!(result.as_bool());
// 触发完整收集:存活正则克隆进新 arena,旧 Box 由 sweep 释放。
vm.reset();
let result = eval(&mut vm, "globalThis.r.test('xxabbcx')").unwrap();
assert!(result.as_bool(), "GC 后正则应仍可匹配");
let result = eval(&mut vm, "globalThis.r.exec('xabbbcx')[0]").unwrap();
assert_eq!(to_str(&vm, result), "abbbc");
}
// --- RegExp constructor ---
#[test]
fn regexp_constructor_creates_object() {
let mut vm = Vm::new();
let result = eval(&mut vm, "typeof new RegExp('abc')").unwrap();
assert_eq!(to_str(&vm, result), "object");
}
#[test]
fn regexp_constructor_with_flags() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new RegExp('abc', 'gi').ignoreCase").unwrap();
assert!(result.as_bool());
}
#[test]
fn regexp_constructor_invalid_pattern_syntax_error() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new RegExp('[')");
assert!(result.is_err() || (result.is_ok() && result.unwrap().is_string()));
}
#[test]
fn regexp_source_property() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new RegExp('hello', 'g').source").unwrap();
assert_eq!(to_str(&vm, result), "hello");
}
#[test]
fn regexp_flags_property() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new RegExp('abc', 'gi').flags").unwrap();
assert_eq!(to_str(&vm, result), "gi");
}
#[test]
fn regexp_global_property() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new RegExp('abc', 'g').global").unwrap();
assert!(result.as_bool());
}
#[test]
fn regexp_multiline_property() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new RegExp('abc', 'm').multiline").unwrap();
assert!(result.as_bool());
}
#[test]
fn regexp_last_index_property() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new RegExp('abc', 'g').lastIndex").unwrap();
assert_eq!(result.as_int(), 0);
}
#[test]
fn regexp_dotall_sticky_unicode_default_false() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new RegExp('abc').dotAll").unwrap();
assert!(!result.as_bool());
}
// --- RegExp.prototype.test ---
#[test]
fn regexp_test_basic() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new RegExp('abc').test('abc')").unwrap();
assert!(result.as_bool());
}
#[test]
fn regexp_test_no_match() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new RegExp('abc').test('xyz')").unwrap();
assert!(!result.as_bool());
}
#[test]
fn regexp_test_case_insensitive() {
let mut vm = Vm::new();
let result = eval(&mut vm, "new RegExp('abc', 'i').test('ABC')").unwrap();
assert!(result.as_bool());
}
#[test]
fn regexp_test_global_flag_iteration() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var re = /a/g; re.test('a')").unwrap();
assert!(result.as_bool());
}
// --- RegExp.prototype.exec ---
#[test]
fn regexp_exec_basic() {
let mut vm = Vm::new();
let result = eval(&mut vm, "/hello/.exec('hello world') !== null").unwrap();
assert!(result.as_bool());
}
#[test]
fn regexp_exec_no_match_returns_null() {
let mut vm = Vm::new();
let result = eval(&mut vm, "/xyz/.exec('hello')").unwrap();
assert!(result.is_null());
}
#[test]
fn regexp_exec_returns_index() {
let mut vm = Vm::new();
let result = eval(&mut vm, "/ell/.exec('hello')").unwrap();
assert!(result.is_object());
}
#[test]
fn regexp_exec_returns_input() {
let mut vm = Vm::new();
let m = eval(&mut vm, "/ell/.exec('hello')").unwrap();
assert!(m.is_object());
}
#[test]
fn regexp_exec_global_last_index_advances() {
let mut vm = Vm::new();
let re = eval(&mut vm, "var re = /a/g; re.exec('aba'); re.lastIndex").unwrap();
assert_eq!(re.as_int(), 1);
}
#[test]
fn regexp_exec_global_resets_on_exhaustion() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var re = /a/g; re.exec('a'); re.exec('a') === null").unwrap();
assert!(result.as_bool());
}
// --- RegExp literal compilation ---
#[test]
fn regexp_literal_basic() {
let mut vm = Vm::new();
let result = eval(&mut vm, "/hello/").unwrap();
assert!(result.is_object());
}
#[test]
fn regexp_literal_with_flags() {
let mut vm = Vm::new();
let result = eval(&mut vm, "/abc/i.test('ABC')").unwrap();
assert!(result.as_bool());
}
#[test]
fn regexp_literal_global_exec() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var re = /a/g; re.exec('aba'); re.lastIndex").unwrap();
assert_eq!(result.as_int(), 1);
}
// --- String integration ---
#[test]
fn string_match_with_regexp() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'hello world'.match(/ell/)").unwrap();
assert!(result.is_object());
}
#[test]
fn string_replace_with_regexp() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'hello'.replace(/ell/, 'ipp')").unwrap();
assert_eq!(to_str(&vm, result), "hippo");
}
#[test]
fn string_replace_with_regexp_global() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'aba'.replace(/a/g, 'c')").unwrap();
assert_eq!(to_str(&vm, result), "cbc");
}
#[test]
fn string_search_with_regexp() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'hello'.search(/ell/)").unwrap();
assert_eq!(result.as_int(), 1);
}
#[test]
fn string_search_not_found() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'hello'.search(/xyz/)").unwrap();
assert_eq!(result.as_int(), -1);
}
#[test]
fn string_split_with_regexp() {
let mut vm = Vm::new();
let result = eval(&mut vm, "'a,b,c'.split(/,/)").unwrap();
assert!(result.is_object());
let obj = unsafe { &*result.as_js_object_ptr() };
assert_eq!(obj.prop_count(), 3);
assert_eq!(to_str(&vm, obj.get_prop_at(1)), "b");
}
// --- toString ---
#[test]
fn regexp_to_string() {
let mut vm = Vm::new();
let result = eval(&mut vm, "/abc/gi.toString()").unwrap();
assert_eq!(to_str(&vm, result), "/abc/gi");
}
// --- Unicode property escapes:Script/Script_Extensions 的 Unknown(Zzzz)取值 ---
// vendor 的 Unicode 表须含 Unknown(未分配码点集),否则合法模式编译期 SyntaxError。
#[test]
fn regexp_unicode_property_script_unknown_matches_unassigned() {
let mut vm = Vm::new();
// U+038B 未分配:Script 与 Script_Extensions 均为 Unknown
let result = eval(&mut vm, "/\\p{Script=Unknown}/u.test(String.fromCodePoint(0x038B))").unwrap();
assert!(result.as_bool(), "未分配码点应匹配 Script=Unknown");
let result = eval(&mut vm, "/\\p{Script_Extensions=Unknown}/u.test(String.fromCodePoint(0x038B))").unwrap();
assert!(result.as_bool(), "未分配码点应匹配 Script_Extensions=Unknown");
}
#[test]
fn regexp_unicode_property_script_unknown_rejects_assigned() {
let mut vm = Vm::new();
// 已分配拉丁字母不属于 Unknown
let result = eval(&mut vm, "/\\p{Script=Unknown}/u.test('d')").unwrap();
assert!(!result.as_bool(), "已分配码点不应匹配 Script=Unknown");
let result = eval(&mut vm, "/\\P{Script=Unknown}/u.test('d')").unwrap();
assert!(result.as_bool(), "取反后已分配码点应匹配");
}
#[test]
fn regexp_unicode_property_script_unknown_aliases() {
let mut vm = Vm::new();
// 单字母缩写与符号名须同义解析
for pattern in [r"\p{sc=Unknown}", r"\p{scx=Unknown}", r"\p{Script=Zzzz}", r"\p{scx=Zzzz}"] {
let source = format!("/{pattern}/u.test(String.fromCodePoint(0x038B))");
let result = eval(&mut vm, &source).unwrap();
assert!(result.as_bool(), "别名 {pattern} 应匹配未分配码点");
}
}
#[test]
fn regexp_unicode_property_script_existing_values_unchanged() {
// 补齐 Unknown 不得扰动既有取值:Adlam 等已分配 Script 行为保持
let mut vm = Vm::new();
let result = eval(&mut vm, "/\\p{Script=Adlam}/u.test(String.fromCodePoint(0x1E900))").unwrap();
assert!(result.as_bool(), "既有 Script 取值行为应保持不变");
let result = eval(&mut vm, "/\\p{Script=Adlam}/u.test('d')").unwrap();
assert!(!result.as_bool(), "既有 Script 取值行为应保持不变");
}