diff --git a/crates/oxide_test262/src/harness.rs b/crates/oxide_test262/src/harness.rs new file mode 100644 index 00000000..859b6776 --- /dev/null +++ b/crates/oxide_test262/src/harness.rs @@ -0,0 +1,146 @@ +//! test262 harness 内嵌注册表与前缀拼接/缓存:`HarnessSources`(21 处 `include_str!`)、`HARNESS`、黑名单、 +//! 前缀拼接与缓存。全部 `include_str!` 路径相对上 3 级到仓库根,依赖本文件平铺 `src/` 一级,禁子目录。 + +use crate::meta::TestMeta; +use std::collections::HashMap; +use std::sync::{Arc, OnceLock, RwLock}; + +/// 内嵌的 test262 harness 辅助脚本注册表(编译期 include_str! 打包)。 +pub(crate) struct HarnessSources { + sources: HashMap<&'static str, &'static str>, +} + +/// harness 前缀缓存键:由测试 `includes` 列表唯一确定。 +pub(crate) type HarnessPrefixCache = HashMap, String>; + +impl HarnessSources { + /// 构建 harness 源注册表(键为文件名,值为编译期内嵌源码)。 + pub(crate) fn new() -> Self { + let mut sources = HashMap::new(); + sources.insert("sta.js", include_str!("../../../tests/test262/harness/sta.js")); + sources.insert("assert.js", include_str!("../../../tests/test262/harness/assert.js")); + sources.insert("propertyHelper.js", include_str!("../../../tests/test262/harness/propertyHelper.js")); + sources.insert("compareArray.js", include_str!("../../../tests/test262/harness/compareArray.js")); + sources.insert("fnGlobalObject.js", include_str!("../../../tests/test262/harness/fnGlobalObject.js")); + sources.insert("nans.js", include_str!("../../../tests/test262/harness/nans.js")); + sources.insert("dateConstants.js", include_str!("../../../tests/test262/harness/dateConstants.js")); + sources.insert( + "decimalToHexString.js", + include_str!("../../../tests/test262/harness/decimalToHexString.js"), + ); + sources.insert("isConstructor.js", include_str!("../../../tests/test262/harness/isConstructor.js")); + sources.insert("nativeErrors.js", include_str!("../../../tests/test262/harness/nativeErrors.js")); + sources.insert( + "nativeFunctionMatcher.js", + include_str!("../../../tests/test262/harness/nativeFunctionMatcher.js"), + ); + sources.insert("regExpUtils.js", include_str!("../../../tests/test262/harness/regExpUtils.js")); + sources.insert( + "assertRelativeDateMs.js", + include_str!("../../../tests/test262/harness/assertRelativeDateMs.js"), + ); + sources.insert( + "wellKnownIntrinsicObjects.js", + include_str!("../../../tests/test262/harness/wellKnownIntrinsicObjects.js"), + ); + sources.insert("typeCoercion.js", include_str!("../../../tests/test262/harness/typeCoercion.js")); + sources.insert("deepEqual.js", include_str!("../../../tests/test262/harness/deepEqual.js")); + sources.insert("testTypedArray.js", include_str!("../../../tests/test262/harness/testTypedArray.js")); + sources.insert("temporalHelpers.js", include_str!("../../../tests/test262/harness/temporalHelpers.js")); + sources.insert("asyncHelpers.js", include_str!("../../../tests/test262/harness/asyncHelpers.js")); + sources.insert("doneprintHandle.js", include_str!("../../../tests/test262/harness/doneprintHandle.js")); + sources.insert("promiseHelper.js", include_str!("../../../tests/test262/harness/promiseHelper.js")); + Self { sources } + } + + /// 按文件名取 harness 源码。 + pub(crate) fn get(&self, name: &str) -> Option<&'static str> { + self.sources.get(name).copied() + } +} + +pub(crate) static HARNESS: OnceLock = OnceLock::new(); + +/// 判断 harness 文件是否在支持范围之外(依赖 Proxy/Intl/async 等未实现特性)。 +fn is_blacklisted_harness(name: &str) -> bool { + matches!( + name, + "testIntl.js" + | "testAtomics.js" + | "atomicsHelper.js" + | "proxyTrapsHelper.js" + | "tcoHelper.js" + | "detachArrayBuffer.js" + | "resizableArrayBufferUtils.js" + | "byteConversionValues.js" + | "compareIterator.js" + | "iteratorZipUtils.js" + ) +} + +/// 生成 `Test262Error` 的 JS prelude(供测试脚本 `assert` 失败时抛出)。 +/// 向拼接源码追加一段带注释标记的 harness 代码块。 +pub(crate) fn append_source_chunk(out: &mut String, name: &str, source: &str) { + out.push_str("\n// ---- test262 harness: "); + out.push_str(name); + out.push_str(" ----\n"); + out.push_str(source); + out.push('\n'); +} + +/// 取测试元数据的 harness 缓存键(即其 `includes` 列表)。 +fn harness_key(meta: &TestMeta) -> Vec { + let mut key = meta.includes.clone(); + if meta.flags.iter().any(|f| f == "onlyStrict") { + key.push("__onlyStrict__".into()); + } + key +} + +/// 按测试元数据拼接完整的 harness 前缀源码(prelude + sta/assert + 各 include)。 +fn build_harness_source(meta: &TestMeta, harness: &HarnessSources) -> Result { + let mut source = String::new(); + // onlyStrict 测试在严格模式下运行:指令必须位于脚本最前(harness 之前)。 + if meta.flags.iter().any(|f| f == "onlyStrict") { + append_source_chunk(&mut source, "use strict", "\"use strict\";"); + } + // Test262Error 由 sta.js 提供;此处不再重复定义(重复函数声明会引发 + // 引擎 prototype 语义错乱,导致 assert.throws 的 constructor 比对失败)。 + append_source_chunk( + &mut source, + "sta.js", + harness.get("sta.js").ok_or_else(|| String::from("unknown harness: sta.js"))?, + ); + append_source_chunk( + &mut source, + "assert.js", + harness + .get("assert.js") + .ok_or_else(|| String::from("unknown harness: assert.js"))?, + ); + for include in &meta.includes { + if is_blacklisted_harness(include) { + return Err(format!("out-of-scope harness: {include}")); + } + let include_source = harness.get(include).ok_or_else(|| format!("unknown harness: {include}"))?; + append_source_chunk(&mut source, include, include_source); + } + Ok(source) +} + +/// 取得(并缓存)测试所需的 harness 前缀源码;缓存键为 includes 组合。 +pub(crate) fn get_harness_prefix( + meta: &TestMeta, harness: &HarnessSources, cache: &Arc>, +) -> Result { + let key = harness_key(meta); + { + let guard = cache.read().unwrap(); + if let Some(prefix) = guard.get(&key) { + return Ok(prefix.clone()); + } + } + + let source = build_harness_source(meta, harness)?; + cache.write().unwrap().insert(key, source.clone()); + Ok(source) +} diff --git a/crates/oxide_test262/src/main.rs b/crates/oxide_test262/src/main.rs index b60db331..57571289 100644 --- a/crates/oxide_test262/src/main.rs +++ b/crates/oxide_test262/src/main.rs @@ -8,16 +8,18 @@ use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::process::Command; use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::{Arc, OnceLock, RwLock}; +use std::sync::{Arc, RwLock}; use std::time::{Duration, Instant}; mod config; mod discovery; +mod harness; mod meta; mod report; mod test262_log; use config::RunConfig; use discovery::{discover_tests, eval_family_excluded, is_skipped, pre_existing_excluded}; +use harness::{append_source_chunk, get_harness_prefix, HarnessPrefixCache, HarnessSources, HARNESS}; use meta::{parse_meta, strip_meta, Negative, TestMeta}; use oxide_log::{Level, LogConfig, Output, SUBSYSTEM_COUNT}; use report::{ @@ -171,146 +173,6 @@ impl RunStats { } } -/// 内嵌的 test262 harness 辅助脚本注册表(编译期 include_str! 打包)。 -struct HarnessSources { - sources: HashMap<&'static str, &'static str>, -} - -/// harness 前缀缓存键:由测试 `includes` 列表唯一确定。 -type HarnessPrefixCache = HashMap, String>; - -impl HarnessSources { - /// 构建 harness 源注册表(键为文件名,值为编译期内嵌源码)。 - fn new() -> Self { - let mut sources = HashMap::new(); - sources.insert("sta.js", include_str!("../../../tests/test262/harness/sta.js")); - sources.insert("assert.js", include_str!("../../../tests/test262/harness/assert.js")); - sources.insert("propertyHelper.js", include_str!("../../../tests/test262/harness/propertyHelper.js")); - sources.insert("compareArray.js", include_str!("../../../tests/test262/harness/compareArray.js")); - sources.insert("fnGlobalObject.js", include_str!("../../../tests/test262/harness/fnGlobalObject.js")); - sources.insert("nans.js", include_str!("../../../tests/test262/harness/nans.js")); - sources.insert("dateConstants.js", include_str!("../../../tests/test262/harness/dateConstants.js")); - sources.insert( - "decimalToHexString.js", - include_str!("../../../tests/test262/harness/decimalToHexString.js"), - ); - sources.insert("isConstructor.js", include_str!("../../../tests/test262/harness/isConstructor.js")); - sources.insert("nativeErrors.js", include_str!("../../../tests/test262/harness/nativeErrors.js")); - sources.insert( - "nativeFunctionMatcher.js", - include_str!("../../../tests/test262/harness/nativeFunctionMatcher.js"), - ); - sources.insert("regExpUtils.js", include_str!("../../../tests/test262/harness/regExpUtils.js")); - sources.insert( - "assertRelativeDateMs.js", - include_str!("../../../tests/test262/harness/assertRelativeDateMs.js"), - ); - sources.insert( - "wellKnownIntrinsicObjects.js", - include_str!("../../../tests/test262/harness/wellKnownIntrinsicObjects.js"), - ); - sources.insert("typeCoercion.js", include_str!("../../../tests/test262/harness/typeCoercion.js")); - sources.insert("deepEqual.js", include_str!("../../../tests/test262/harness/deepEqual.js")); - sources.insert("testTypedArray.js", include_str!("../../../tests/test262/harness/testTypedArray.js")); - sources.insert("temporalHelpers.js", include_str!("../../../tests/test262/harness/temporalHelpers.js")); - sources.insert("asyncHelpers.js", include_str!("../../../tests/test262/harness/asyncHelpers.js")); - sources.insert("doneprintHandle.js", include_str!("../../../tests/test262/harness/doneprintHandle.js")); - sources.insert("promiseHelper.js", include_str!("../../../tests/test262/harness/promiseHelper.js")); - Self { sources } - } - - /// 按文件名取 harness 源码。 - fn get(&self, name: &str) -> Option<&'static str> { - self.sources.get(name).copied() - } -} - -static HARNESS: OnceLock = OnceLock::new(); - -/// 判断 harness 文件是否在支持范围之外(依赖 Proxy/Intl/async 等未实现特性)。 -fn is_blacklisted_harness(name: &str) -> bool { - matches!( - name, - "testIntl.js" - | "testAtomics.js" - | "atomicsHelper.js" - | "proxyTrapsHelper.js" - | "tcoHelper.js" - | "detachArrayBuffer.js" - | "resizableArrayBufferUtils.js" - | "byteConversionValues.js" - | "compareIterator.js" - | "iteratorZipUtils.js" - ) -} - -/// 生成 `Test262Error` 的 JS prelude(供测试脚本 `assert` 失败时抛出)。 -/// 向拼接源码追加一段带注释标记的 harness 代码块。 -fn append_source_chunk(out: &mut String, name: &str, source: &str) { - out.push_str("\n// ---- test262 harness: "); - out.push_str(name); - out.push_str(" ----\n"); - out.push_str(source); - out.push('\n'); -} - -/// 取测试元数据的 harness 缓存键(即其 `includes` 列表)。 -fn harness_key(meta: &TestMeta) -> Vec { - let mut key = meta.includes.clone(); - if meta.flags.iter().any(|f| f == "onlyStrict") { - key.push("__onlyStrict__".into()); - } - key -} - -/// 按测试元数据拼接完整的 harness 前缀源码(prelude + sta/assert + 各 include)。 -fn build_harness_source(meta: &TestMeta, harness: &HarnessSources) -> Result { - let mut source = String::new(); - // onlyStrict 测试在严格模式下运行:指令必须位于脚本最前(harness 之前)。 - if meta.flags.iter().any(|f| f == "onlyStrict") { - append_source_chunk(&mut source, "use strict", "\"use strict\";"); - } - // Test262Error 由 sta.js 提供;此处不再重复定义(重复函数声明会引发 - // 引擎 prototype 语义错乱,导致 assert.throws 的 constructor 比对失败)。 - append_source_chunk( - &mut source, - "sta.js", - harness.get("sta.js").ok_or_else(|| String::from("unknown harness: sta.js"))?, - ); - append_source_chunk( - &mut source, - "assert.js", - harness - .get("assert.js") - .ok_or_else(|| String::from("unknown harness: assert.js"))?, - ); - for include in &meta.includes { - if is_blacklisted_harness(include) { - return Err(format!("out-of-scope harness: {include}")); - } - let include_source = harness.get(include).ok_or_else(|| format!("unknown harness: {include}"))?; - append_source_chunk(&mut source, include, include_source); - } - Ok(source) -} - -/// 取得(并缓存)测试所需的 harness 前缀源码;缓存键为 includes 组合。 -fn get_harness_prefix( - meta: &TestMeta, harness: &HarnessSources, cache: &Arc>, -) -> Result { - let key = harness_key(meta); - { - let guard = cache.read().unwrap(); - if let Some(prefix) = guard.get(&key) { - return Ok(prefix.clone()); - } - } - - let source = build_harness_source(meta, harness)?; - cache.write().unwrap().insert(key, source.clone()); - Ok(source) -} - /// 在 catch_unwind 保护下运行单个测试,把引擎 panic 记为失败。 #[expect(clippy::too_many_arguments)] fn run_test(