mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-25 19:21:11 +08:00
refactor: test262 runner 元数据与运行配置独立成模块
This commit is contained in:
@@ -0,0 +1,95 @@
|
|||||||
|
//! test262 runner 运行配置:`RunConfig` 结构、CLI 解析(`parse`)与用法输出(`usage`)。
|
||||||
|
//! 9 字段与 3 关联函数放宽为 `pub(crate)`;`leak_check_interval` 缺省 1000,`usage` 全文即 `--help` 用户可见输出,逐字保留。
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// 运行配置:test262 根目录、路径过滤器及各选项开关。
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub(crate) struct RunConfig {
|
||||||
|
pub(crate) test262_root: Option<PathBuf>,
|
||||||
|
pub(crate) filter: Option<String>,
|
||||||
|
pub(crate) no_skip: bool,
|
||||||
|
pub(crate) supervise: bool,
|
||||||
|
pub(crate) leak_check: bool,
|
||||||
|
pub(crate) leak_check_interval: usize,
|
||||||
|
/// 关闭 liveness/精确 DCE/RegAlloc 链(on/off 对比基础设施)。
|
||||||
|
pub(crate) no_regalloc: bool,
|
||||||
|
/// 逐测试打印 PASS/FAIL/SKIP(on/off 结果集合对比用)。
|
||||||
|
pub(crate) verbose: bool,
|
||||||
|
/// 汇总尾部不打印 FAIL 清单。
|
||||||
|
pub(crate) no_fail_list: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RunConfig {
|
||||||
|
/// 默认运行配置。
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
test262_root: None,
|
||||||
|
filter: None,
|
||||||
|
no_skip: false,
|
||||||
|
supervise: false,
|
||||||
|
leak_check: false,
|
||||||
|
leak_check_interval: 1000,
|
||||||
|
no_regalloc: false,
|
||||||
|
verbose: false,
|
||||||
|
no_fail_list: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 解析命令行参数为运行配置;未知选项或参数过多返回错误。
|
||||||
|
pub(crate) fn parse(args: &[String]) -> Result<Self, String> {
|
||||||
|
let mut config = Self::new();
|
||||||
|
let mut positional = Vec::new();
|
||||||
|
|
||||||
|
for arg in args.iter().skip(1) {
|
||||||
|
match arg.as_str() {
|
||||||
|
"--no-skip" => config.no_skip = true,
|
||||||
|
"--no-regalloc" => config.no_regalloc = true,
|
||||||
|
"--verbose" => config.verbose = true,
|
||||||
|
"--supervise" => config.supervise = true,
|
||||||
|
"--leak-check" => config.leak_check = true,
|
||||||
|
"--no-fail-list" => config.no_fail_list = true,
|
||||||
|
"--help" | "-h" => return Err(Self::usage()),
|
||||||
|
_ if arg.starts_with("--leak-check-interval=") => {
|
||||||
|
config.leak_check_interval =
|
||||||
|
arg.strip_prefix("--leak-check-interval=").unwrap().parse().unwrap_or(1000);
|
||||||
|
}
|
||||||
|
_ if arg.starts_with("--") => return Err(format!("unknown option: {arg}\n\n{}", Self::usage())),
|
||||||
|
_ => positional.push(arg.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(root) = positional.first() {
|
||||||
|
config.test262_root = Some(PathBuf::from(root));
|
||||||
|
}
|
||||||
|
if let Some(filter) = positional.get(1) {
|
||||||
|
config.filter = Some(filter.clone());
|
||||||
|
}
|
||||||
|
if positional.len() > 2 {
|
||||||
|
return Err(format!("too many positional arguments\n\n{}", Self::usage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 打印用法说明。
|
||||||
|
pub(crate) fn usage() -> String {
|
||||||
|
"usage: test262-runner [--no-skip] [--no-regalloc] [--verbose] [--supervise] [--leak-check] [--leak-check-interval=N] [test262-root] [path-filter]\n\
|
||||||
|
\n\
|
||||||
|
--no-skip Run capability-excluded tests and count unsupported compile/runtime results as failures.\n\
|
||||||
|
--no-regalloc Disable the liveness/precise-DCE/RegAlloc compiler chain (vregs stay as physical numbers).\n\
|
||||||
|
--verbose Print one PASS/FAIL/SKIP line per test (for on/off result-set comparison).\n\
|
||||||
|
--no-fail-list Do not print the per-path FAIL list at the end of the run.\n\
|
||||||
|
--supervise Run the suite as single-worker child-process windows with a hard per-test timeout and\n\
|
||||||
|
\x20 automatic resume past any hanging/crashing test. A hang or crash is reported by path.\n\
|
||||||
|
--leak-check Monitor session_object_ptrs, session_bytes, code_forge.len(), symbol_registry.len() every\n\
|
||||||
|
\x20 --leak-check-interval tests (default 1000). Flags sustained linear growth (R^2>0.9).\n\
|
||||||
|
\n\
|
||||||
|
supervised-mode env tunables:\n\
|
||||||
|
\x20 OXIDE_TEST262_TIMEOUT_SECS per-test wall-clock timeout (default 10)\n\
|
||||||
|
\x20 OXIDE_TEST262_WINDOW tests per window (default 5000)\n\
|
||||||
|
\x20 OXIDE_TEST262_SUPERVISORS concurrent windows (default = available parallelism)\n\
|
||||||
|
\x20 OXIDE_TEST262_STARTUP_GRACE_SECS grace for a child's first heartbeat (default 60)"
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ use oxide_compiler::compiler::Compiler;
|
|||||||
use oxide_kernel::kernel::{KernelConfig, KernelCore};
|
use oxide_kernel::kernel::{KernelConfig, KernelCore};
|
||||||
use oxide_types::value::JsValue;
|
use oxide_types::value::JsValue;
|
||||||
use oxide_vm::vm::Vm;
|
use oxide_vm::vm::Vm;
|
||||||
use serde::Deserialize;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
@@ -13,8 +12,12 @@ use std::sync::{Arc, OnceLock, RwLock};
|
|||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
mod meta;
|
||||||
mod report;
|
mod report;
|
||||||
mod test262_log;
|
mod test262_log;
|
||||||
|
use config::RunConfig;
|
||||||
|
use meta::{parse_meta, strip_meta, Negative, TestMeta};
|
||||||
use oxide_log::{Level, LogConfig, Output, SUBSYSTEM_COUNT};
|
use oxide_log::{Level, LogConfig, Output, SUBSYSTEM_COUNT};
|
||||||
use report::{
|
use report::{
|
||||||
append_fail_log, extract_not_callable_subkey, first_line, format_fail_categories, format_fail_groupings,
|
append_fail_log, extract_not_callable_subkey, first_line, format_fail_categories, format_fail_groupings,
|
||||||
@@ -27,38 +30,6 @@ std::thread_local! {
|
|||||||
static CURRENT_TEST_PATH: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
|
static CURRENT_TEST_PATH: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 测试头部 YAML 元数据中的 `negative` 段:声明期望的失败阶段与错误类型。
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
struct Negative {
|
|
||||||
phase: String,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
error_type: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// test262 测试文件头部 `/*--- ... ---*/` 段解析出的元数据
|
|
||||||
/// (description / flags / includes / features / negative 等)。
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
struct TestMeta {
|
|
||||||
#[serde(default)]
|
|
||||||
description: String,
|
|
||||||
#[serde(default)]
|
|
||||||
flags: Vec<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
includes: Vec<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
features: Vec<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
negative: Option<Negative>,
|
|
||||||
#[serde(default)]
|
|
||||||
es5id: String,
|
|
||||||
#[serde(default)]
|
|
||||||
es6id: String,
|
|
||||||
#[serde(default)]
|
|
||||||
esid: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 单个测试的判定结果:通过 / 失败 / 跳过(各带说明消息)。
|
/// 单个测试的判定结果:通过 / 失败 / 跳过(各带说明消息)。
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -106,25 +77,6 @@ impl TestResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 从测试源码头部解析 `/*--- YAML ---*/` 元数据;无该头部返回 None。
|
|
||||||
fn parse_meta(source: &str) -> Option<TestMeta> {
|
|
||||||
let header_start = source.find("/*---")?;
|
|
||||||
let header = &source[header_start..];
|
|
||||||
let header = header.strip_prefix("/*---")?;
|
|
||||||
let end = header.find("---*/")?;
|
|
||||||
let yaml_body = &header[..end];
|
|
||||||
let yaml_body = yaml_body.trim();
|
|
||||||
serde_yaml::from_str::<TestMeta>(yaml_body).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 剥离测试源码头部的 YAML 元数据段,返回纯 JS 代码。
|
|
||||||
fn strip_meta(source: &str) -> &str {
|
|
||||||
if let Some(pos) = source.find("---*/") {
|
|
||||||
return source[pos + 5..].trim_start();
|
|
||||||
}
|
|
||||||
source
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 全部已运行测试的累计统计:通过/失败/跳过计数、总耗时、失败原因分类
|
/// 全部已运行测试的累计统计:通过/失败/跳过计数、总耗时、失败原因分类
|
||||||
/// 与逐路径失败记录(fail_records,供汇总区聚合报告)。
|
/// 与逐路径失败记录(fail_records,供汇总区聚合报告)。
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -218,23 +170,6 @@ impl RunStats {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 运行配置:test262 根目录、路径过滤器及各选项开关。
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
struct RunConfig {
|
|
||||||
test262_root: Option<PathBuf>,
|
|
||||||
filter: Option<String>,
|
|
||||||
no_skip: bool,
|
|
||||||
supervise: bool,
|
|
||||||
leak_check: bool,
|
|
||||||
leak_check_interval: usize,
|
|
||||||
/// 关闭 liveness/精确 DCE/RegAlloc 链(on/off 对比基础设施)。
|
|
||||||
no_regalloc: bool,
|
|
||||||
/// 逐测试打印 PASS/FAIL/SKIP(on/off 结果集合对比用)。
|
|
||||||
verbose: bool,
|
|
||||||
/// 汇总尾部不打印 FAIL 清单。
|
|
||||||
no_fail_list: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 内嵌的 test262 harness 辅助脚本注册表(编译期 include_str! 打包)。
|
/// 内嵌的 test262 harness 辅助脚本注册表(编译期 include_str! 打包)。
|
||||||
struct HarnessSources {
|
struct HarnessSources {
|
||||||
sources: HashMap<&'static str, &'static str>,
|
sources: HashMap<&'static str, &'static str>,
|
||||||
@@ -375,80 +310,6 @@ fn get_harness_prefix(
|
|||||||
Ok(source)
|
Ok(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunConfig {
|
|
||||||
/// 默认运行配置。
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
test262_root: None,
|
|
||||||
filter: None,
|
|
||||||
no_skip: false,
|
|
||||||
supervise: false,
|
|
||||||
leak_check: false,
|
|
||||||
leak_check_interval: 1000,
|
|
||||||
no_regalloc: false,
|
|
||||||
verbose: false,
|
|
||||||
no_fail_list: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 解析命令行参数为运行配置;未知选项或参数过多返回错误。
|
|
||||||
fn parse(args: &[String]) -> Result<Self, String> {
|
|
||||||
let mut config = Self::new();
|
|
||||||
let mut positional = Vec::new();
|
|
||||||
|
|
||||||
for arg in args.iter().skip(1) {
|
|
||||||
match arg.as_str() {
|
|
||||||
"--no-skip" => config.no_skip = true,
|
|
||||||
"--no-regalloc" => config.no_regalloc = true,
|
|
||||||
"--verbose" => config.verbose = true,
|
|
||||||
"--supervise" => config.supervise = true,
|
|
||||||
"--leak-check" => config.leak_check = true,
|
|
||||||
"--no-fail-list" => config.no_fail_list = true,
|
|
||||||
"--help" | "-h" => return Err(Self::usage()),
|
|
||||||
_ if arg.starts_with("--leak-check-interval=") => {
|
|
||||||
config.leak_check_interval =
|
|
||||||
arg.strip_prefix("--leak-check-interval=").unwrap().parse().unwrap_or(1000);
|
|
||||||
}
|
|
||||||
_ if arg.starts_with("--") => return Err(format!("unknown option: {arg}\n\n{}", Self::usage())),
|
|
||||||
_ => positional.push(arg.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(root) = positional.first() {
|
|
||||||
config.test262_root = Some(PathBuf::from(root));
|
|
||||||
}
|
|
||||||
if let Some(filter) = positional.get(1) {
|
|
||||||
config.filter = Some(filter.clone());
|
|
||||||
}
|
|
||||||
if positional.len() > 2 {
|
|
||||||
return Err(format!("too many positional arguments\n\n{}", Self::usage()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 打印用法说明。
|
|
||||||
fn usage() -> String {
|
|
||||||
"usage: test262-runner [--no-skip] [--no-regalloc] [--verbose] [--supervise] [--leak-check] [--leak-check-interval=N] [test262-root] [path-filter]\n\
|
|
||||||
\n\
|
|
||||||
--no-skip Run capability-excluded tests and count unsupported compile/runtime results as failures.\n\
|
|
||||||
--no-regalloc Disable the liveness/precise-DCE/RegAlloc compiler chain (vregs stay as physical numbers).\n\
|
|
||||||
--verbose Print one PASS/FAIL/SKIP line per test (for on/off result-set comparison).\n\
|
|
||||||
--no-fail-list Do not print the per-path FAIL list at the end of the run.\n\
|
|
||||||
--supervise Run the suite as single-worker child-process windows with a hard per-test timeout and\n\
|
|
||||||
\x20 automatic resume past any hanging/crashing test. A hang or crash is reported by path.\n\
|
|
||||||
--leak-check Monitor session_object_ptrs, session_bytes, code_forge.len(), symbol_registry.len() every\n\
|
|
||||||
\x20 --leak-check-interval tests (default 1000). Flags sustained linear growth (R^2>0.9).\n\
|
|
||||||
\n\
|
|
||||||
supervised-mode env tunables:\n\
|
|
||||||
\x20 OXIDE_TEST262_TIMEOUT_SECS per-test wall-clock timeout (default 10)\n\
|
|
||||||
\x20 OXIDE_TEST262_WINDOW tests per window (default 5000)\n\
|
|
||||||
\x20 OXIDE_TEST262_SUPERVISORS concurrent windows (default = available parallelism)\n\
|
|
||||||
\x20 OXIDE_TEST262_STARTUP_GRACE_SECS grace for a child's first heartbeat (default 60)"
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 按测试元数据的 flags/features 判断是否应跳过,返回跳过原因(None 表示不跳过)。
|
/// 按测试元数据的 flags/features 判断是否应跳过,返回跳过原因(None 表示不跳过)。
|
||||||
fn is_skipped(meta: &TestMeta) -> Option<String> {
|
fn is_skipped(meta: &TestMeta) -> Option<String> {
|
||||||
for flag in &meta.flags {
|
for flag in &meta.flags {
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
//! test262 测试文件头部 YAML 元数据结构(`TestMeta`/`Negative`)与解析剥离函数(`parse_meta`/`strip_meta`)。
|
||||||
|
//! 结构与函数放宽为 `pub(crate)` 供 crate 内跨模块访问;YAML 解析走全限定 `serde_yaml::from_str`。
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// 测试头部 YAML 元数据中的 `negative` 段:声明期望的失败阶段与错误类型。
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) struct Negative {
|
||||||
|
pub(crate) phase: String,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub(crate) error_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// test262 测试文件头部 `/*--- ... ---*/` 段解析出的元数据
|
||||||
|
/// (description / flags / includes / features / negative 等)。
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) struct TestMeta {
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) description: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) flags: Vec<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) includes: Vec<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) features: Vec<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) negative: Option<Negative>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) es5id: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) es6id: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) esid: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 从测试源码头部解析 `/*--- YAML ---*/` 元数据;无该头部返回 None。
|
||||||
|
pub(crate) fn parse_meta(source: &str) -> Option<TestMeta> {
|
||||||
|
let header_start = source.find("/*---")?;
|
||||||
|
let header = &source[header_start..];
|
||||||
|
let header = header.strip_prefix("/*---")?;
|
||||||
|
let end = header.find("---*/")?;
|
||||||
|
let yaml_body = &header[..end];
|
||||||
|
let yaml_body = yaml_body.trim();
|
||||||
|
serde_yaml::from_str::<TestMeta>(yaml_body).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 剥离测试源码头部的 YAML 元数据段,返回纯 JS 代码。
|
||||||
|
pub(crate) fn strip_meta(source: &str) -> &str {
|
||||||
|
if let Some(pos) = source.find("---*/") {
|
||||||
|
return source[pos + 5..].trim_start();
|
||||||
|
}
|
||||||
|
source
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user