Files
OxideJS-New/crates/oxide_kernel/tests/string_forge_tests.rs
T

247 lines
9.2 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 oxide_kernel::string_forge::{
decode_key, encode_key, single_char_ptr, small_int_ptr, source_escape, source_escape_to_key, typeof_string_ptr,
PermInterner,
};
use oxide_types::object::JsString;
#[test]
fn intern_dedup() {
let interner = PermInterner::new();
let (i1, h1) = interner.intern("abc");
let (i2, h2) = interner.intern("abc");
assert_eq!(i1, i2);
assert_eq!(h1, h2);
}
#[test]
fn intern_different() {
let interner = PermInterner::new();
let (i1, _) = interner.intern("x");
let (i2, _) = interner.intern("y");
assert_ne!(i1, i2);
}
#[test]
fn lookup_zero_clone() {
let interner = PermInterner::new();
let (id, _) = interner.intern("hello");
assert_eq!(interner.lookup(id), Some("hello"));
}
#[test]
fn lookup_not_found() {
let interner = PermInterner::new();
assert_eq!(interner.lookup(99999), None);
}
#[test]
fn entry_count_monotonic() {
let interner = PermInterner::new();
assert_eq!(interner.entry_count(), 0);
interner.intern("a");
interner.intern("b");
interner.intern("a");
assert_eq!(interner.entry_count(), 2);
}
#[test]
fn many_unique_no_collision() {
let interner = PermInterner::new();
for i in 0..10_000 {
let s = format!("key{i}");
let (id, _) = interner.intern(&s);
assert_eq!(interner.lookup(id), Some(&*Box::leak(s.into_boxed_str())));
}
assert_eq!(interner.entry_count(), 10_000);
}
#[test]
fn string_ptr_roundtrip() {
let interner = PermInterner::new();
let (id, _) = interner.intern("perm");
let ptr = interner.string_ptr(id);
assert_eq!(unsafe { (*ptr).as_str() }, "perm");
// 二次调用返回同一稳定指针(仅物化一次)。
assert_eq!(interner.string_ptr(id), ptr);
}
#[test]
fn single_char_ptr_idempotent() {
let a = single_char_ptr(b'a');
assert_eq!(unsafe { (*a).as_str() }, "a");
// 二次调用返回同一稳定指针(仅物化一次)。
assert_eq!(single_char_ptr(b'a'), a);
}
#[test]
fn single_char_table_full_ascii() {
// 全表 128 条目均可物化且内容为对应的单字符文本。
for b in 0u8..=127 {
let ptr = single_char_ptr(b);
assert_eq!(unsafe { (*ptr).as_str() }, (b as char).to_string());
}
}
#[test]
fn typeof_table_content_and_stability() {
// 8 个 typeof 结果串与下标约定一一对应,且二次调用返回同一稳定指针。
let texts = ["undefined", "object", "boolean", "number", "string", "symbol", "bigint", "function"];
for (i, t) in texts.iter().enumerate() {
let ptr = typeof_string_ptr(i as u8);
assert_eq!(unsafe { (*ptr).as_str() }, *t);
assert_eq!(typeof_string_ptr(i as u8), ptr, "下标 {i} 二次调用应返回同一指针");
}
}
#[test]
fn encode_key_identity_and_escapes() {
// 良形且无 FFFD(含超平面良配对)编码为恒等文本。
assert_eq!(encode_key(&"abc🚀".encode_utf16().collect::<Vec<u16>>()), "abc🚀");
// 孤立 surrogate:FFFD + 4 位小写十六进制。
assert_eq!(encode_key(&[0xD800]), "\u{FFFD}d800");
assert_eq!(encode_key(&[0xDFFF]), "\u{FFFD}dfff");
assert_eq!(encode_key(&[0xDBFF, 0x61]), "\u{FFFD}dbffa");
// FFFD 单元:FFFD + 字面 "fffd"。
assert_eq!(encode_key(&[0xFFFD]), "\u{FFFD}fffd");
// 两种不同单元序列的键文本互异(解码可区分)。
assert_ne!(encode_key(&[0xFFFD]), encode_key(&[0xD800]));
// 文本 [FFFD,'d','8','0','0'] 与单元 [D800] 的键必须可区分(防物化串键)。
assert_ne!(encode_key(&[0xFFFD, 0x64, 0x38, 0x30, 0x30]), encode_key(&[0xD800]));
}
#[test]
fn encode_decode_roundtrip() {
let cases: Vec<Vec<u16>> = vec![
Vec::new(),
"a".encode_utf16().collect(),
"abc🚀😀".encode_utf16().collect(),
vec![0xD800, 0x42, 0xDC00],
vec![0xFFFD, 0xFFFD, 0x41, 0xFFFD, 0x66, 0x66, 0x66, 0x64],
vec![0xFFFF, 0x0041, 0xDBFF, 0xDC00, 0xD800],
];
for units in &cases {
let key = encode_key(units);
assert_eq!(decode_key(&key), units.as_slice(), "roundtrip 失败: key={key:?}");
}
}
#[test]
fn decode_key_defensive_raw_fffd() {
// 裸 FFFD(文本末尾或后跟非转义文本)还原为 FFFD 单元,后续字符不被吞。
assert_eq!(decode_key("a\u{FFFD}"), &[0x61, 0xFFFD]);
assert_eq!(decode_key("a\u{FFFD}zz"), &[0x61, 0xFFFD, 0x7A, 0x7A]);
}
#[test]
fn source_escape_marker_forms() {
// 孤立 surrogate / FFFD → 源码域 \u 转义形态;配对与 BMP 恒等。
assert_eq!(source_escape(&[0xD800]), "\\ud800");
assert_eq!(source_escape(&[0xDFFF]), "\\udfff");
assert_eq!(source_escape(&[0xFFFD]), "\\ufffd");
assert_eq!(source_escape(&[0x61, 0xD800, 0x62]), "a\\ud800b");
assert_eq!(source_escape(&[0xD83D, 0xDE00]), "\u{1F600}");
}
/// 源码是 JS 程序,反斜杠为语法字符:原样透传,转义文本形态逐字保留
/// (正则/字符串字面量内 `\u{1d306}`、`\\` 等序列不得被改写)。
#[test]
fn source_escape_backslash_passthrough() {
// 正则字面量源文本(u 模式 \u{..} 转义):逐字恒等。
let re_src: Vec<u16> = "/\\u{1d306}/u".encode_utf16().collect();
assert_eq!(source_escape(&re_src), "/\\u{1d306}/u");
// 转义文本(反斜杠 + "ud800" 字面文本)与孤立 surrogate 单元同形:
// 源码域中 `\ud800` 文本即孤立 surrogate 的源码表示,两者编码同一
// 源码文本是设计使然(源码即程序,oxc 只见转义文本)。
let text: Vec<u16> = "\\ud800".encode_utf16().collect();
assert_eq!(source_escape(&text), "\\ud800");
assert_eq!(source_escape(&[0xD800]), source_escape(&text));
// 双反斜杠(字面量内转义反斜杠)恒等。
let dbl: Vec<u16> = "\\\\".encode_utf16().collect();
assert_eq!(source_escape(&dbl), "\\\\");
}
/// 键域闭环:`decode_key ∘ source_escape_to_key ∘ source_escape` 对单元
/// 序列恒等(注入 marker 还原、用户转义文本透传、双反斜杠与 astral 对)。
#[test]
fn source_escape_roundtrip_via_key() {
let cases: Vec<Vec<u16>> = vec![
vec![0xD800],
vec![0xDFFF],
vec![0xFFFD],
vec![0x5C],
// 用户字面转义文本(非 marker 值域 / \u{..} 形态):逐字透传。
vec![0x5C, 0x75, 0x30, 0x30, 0x34, 0x31],
vec![0x5C, 0x75, 0x7B, 0x31, 0x64, 0x33, 0x30, 0x36, 0x7D],
vec![0x5C, 0x5C],
// 数据反斜杠紧邻注入 marker(`\\ud800`/`\\ufffd` 拼接形态)。
vec![0x5C, 0xD800],
vec![0x5C, 0xFFFD],
// FFFD 单元后跟 "d800" 文本:marker 消费恰 4 字符,不吞后续。
vec![0xFFFD, 0x64, 0x38, 0x30, 0x30],
vec![0x66, 0x66, 0x66, 0x64],
vec![0xD83D, 0xDE00],
];
for units in &cases {
let escaped = source_escape(units);
let key = source_escape_to_key(&escaped);
assert_eq!(&decode_key(&key), units.as_slice(), "roundtrip 失败: escaped={escaped:?}");
}
}
#[test]
fn string_ptr_materializes_units() {
let interner = PermInterner::new();
// 良形键:物化内容与键文本逐字一致,且二次调用返回同一稳定指针。
let (id, _) = interner.intern("perm");
let ptr = interner.string_ptr(id);
assert_eq!(unsafe { (*ptr).as_str() }, "perm");
assert_eq!(interner.string_ptr(id), ptr);
// 含转义的键:物化出孤立 surrogate 单元形态。
let (id2, _) = interner.intern(&encode_key(&[0xD800]));
let ptr2 = interner.string_ptr(id2);
assert!(unsafe { (*ptr2).has_lone_surrogate() });
assert_eq!(unsafe { (*ptr2).units() }, &[0xD800][..]);
}
#[test]
fn perm_table_concurrent_first_use_unique_ptr() {
// 并发首用竞态回归:96 线程同一时刻命中冷表槽,强制多线程同入慢路径。
// 所有调用方必须拿到同一稳定指针,且该指针恒指向存活的 JsString。
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
const N: usize = 96;
let go = Arc::new(AtomicBool::new(false));
let mut handles = Vec::with_capacity(N);
for _ in 0..N {
let go = Arc::clone(&go);
handles.push(std::thread::spawn(move || {
while !go.load(Ordering::Acquire) {
std::hint::spin_loop();
}
// 裸指针 !Send,跨线程以 usize 传递,出口再转回。
let (i, c, t) = (small_int_ptr(42).unwrap(), single_char_ptr(b'x'), typeof_string_ptr(1));
(i as usize, c as usize, t as usize)
}));
}
go.store(true, Ordering::Release);
let mut results = Vec::with_capacity(N);
for h in handles {
results.push(h.join().expect("竞态线程"));
}
let (i0, c0, t0) = results[0];
for (i, c, t) in &results[1..] {
assert_eq!(*i, i0, "小整数表首用应全局唯一指针");
assert_eq!(*c, c0, "单字符表首用应全局唯一指针");
assert_eq!(*t, t0, "typeof 表首用应全局唯一指针");
}
// 返回指针必须指向存活 JsString:内容与长度可回读。
assert_eq!(unsafe { (*(i0 as *const JsString)).as_str() }, "42");
assert_eq!(unsafe { (*(c0 as *const JsString)).as_str() }, "x");
let t0 = t0 as *const JsString;
assert_eq!(unsafe { (*t0).as_str() }, "object");
assert_eq!(unsafe { (*t0).utf16_len() }, 6);
}