mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +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_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 {
|
||||
|
||||
@@ -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