refactor: test262 runner 元数据与运行配置独立成模块

This commit is contained in:
2026-09-16 12:03:25 +00:00
parent f06556cab2
commit 2c146aee6c
3 changed files with 154 additions and 143 deletions
+95
View File
@@ -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 -143
View File
@@ -4,7 +4,6 @@ use oxide_compiler::compiler::Compiler;
use oxide_kernel::kernel::{KernelConfig, KernelCore};
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
use serde::Deserialize;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::process::Command;
@@ -13,8 +12,12 @@ use std::sync::{Arc, OnceLock, RwLock};
use std::time::{Duration, Instant};
use walkdir::WalkDir;
mod config;
mod meta;
mod report;
mod test262_log;
use config::RunConfig;
use meta::{parse_meta, strip_meta, Negative, TestMeta};
use oxide_log::{Level, LogConfig, Output, SUBSYSTEM_COUNT};
use report::{
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()) };
}
/// 测试头部 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)]
#[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,供汇总区聚合报告)。
#[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! 打包)。
struct HarnessSources {
sources: HashMap<&'static str, &'static str>,
@@ -375,80 +310,6 @@ fn get_harness_prefix(
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 表示不跳过)。
fn is_skipped(meta: &TestMeta) -> Option<String> {
for flag in &meta.flags {
+55
View File
@@ -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
}