diff --git a/crates/oxide_test262/src/config.rs b/crates/oxide_test262/src/config.rs index ff086333..dcc3c132 100644 --- a/crates/oxide_test262/src/config.rs +++ b/crates/oxide_test262/src/config.rs @@ -36,6 +36,12 @@ impl RunConfig { } } + /// 监督模式判定:`--supervise` 且非监督派生的子进程(子进程不重入监督)。 + /// 路径 filter 不参与判定——filter 在子进程内逐测试应用,不构成监督屏障。 + pub(crate) fn supervised(&self, is_chunk_child: bool) -> bool { + self.supervise && !is_chunk_child + } + /// 解析命令行参数为运行配置;未知选项或参数过多返回错误。 pub(crate) fn parse(args: &[String]) -> Result { let mut config = Self::new(); @@ -82,14 +88,26 @@ impl RunConfig { --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\ + \x20 Combines with [path-filter]: the filter is applied per-test inside each child window.\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)" + env tunables (all optional):\n\ + \x20 OXIDE_LOG per-subsystem log levels (default off)\n\ + \x20 OXIDE_TEST262_WORKERS worker threads in parallel mode (default = available parallelism, 4 under --no-skip)\n\ + \x20 OXIDE_TEST262_WINDOW supervised tests per child window (default 5000)\n\ + \x20 OXIDE_TEST262_TIMEOUT_SECS per-test wall-clock timeout in supervised mode (default 10)\n\ + \x20 OXIDE_TEST262_STARTUP_GRACE_SECS grace for a child's first heartbeat (default 60)\n\ + \x20 OXIDE_TEST262_SUPERVISORS concurrent supervised windows (default 16)\n\ + \x20 OXIDE_TEST262_RUNNING_LOG log every test as it starts (any value)\n\ + \x20 OXIDE_TEST262_HEARTBEAT heartbeat file path (supervised/chunked)\n\ + \x20 OXIDE_TEST262_CHUNK_SIZE tests per child in chunked mode\n\ + \x20 OXIDE_TEST262_CHILD_CHUNK marks a spawned child as chunk worker (any value)\n\ + \x20 OXIDE_TEST262_ALLOW_FAIL_EXIT child may exit 1 on fail (any value)\n\ + \x20 OXIDE_TEST262_KERNEL_BATCH kernel rebuilds every N tests (default 5000, 1000 under --no-skip)\n\ + \x20 OXIDE_TEST262_LOG_LEVEL runner log level (default info)\n\ + \x20 OXIDE_SKIP_UNTIL skip test indexes below N (chunking/supervise resume)\n\ + \x20 OXIDE_MAX_TESTS run at most N tests after skip point" .into() } } @@ -142,6 +160,18 @@ mod tests { assert_eq!(help, RunConfig::usage()); } + /// 监督判定三态:开启时 filter 不否决、子进程不重入、未开启恒 false。 + #[test] + fn supervised_predicate_ignores_filter() { + let mut cfg = RunConfig::new(); + cfg.supervise = true; + cfg.filter = Some("language".to_string()); + assert!(cfg.supervised(false), "filter 不得否决监督"); + assert!(!cfg.supervised(true), "子进程不得重入监督"); + let off = RunConfig::new(); + assert!(!off.supervised(false)); + } + /// interval 非数字回落缺省 1000;位置参数超过 2 个返回错误。 #[test] fn parse_interval_fallback_and_positional_limit() { diff --git a/crates/oxide_test262/src/main.rs b/crates/oxide_test262/src/main.rs index 7eada858..4bd1bf2d 100644 --- a/crates/oxide_test262/src/main.rs +++ b/crates/oxide_test262/src/main.rs @@ -69,6 +69,11 @@ fn print_fail_categories(stats: &RunStats, paths: &[PathBuf]) { /// 汇总并打印统计;任何失败使返回值为 false(进程退出码 1)。 fn run_tests() -> bool { let args: Vec = std::env::args().collect(); + // 帮助走 stdout 并成功退出,与错误路径(stderr + 退出码 1)分离。 + if args.iter().any(|a| a == "--help" || a == "-h") { + println!("{}", RunConfig::usage()); + return true; + } let config = match RunConfig::parse(&args) { Ok(config) => config, Err(msg) => { @@ -147,9 +152,6 @@ fn run_tests() -> bool { let log_running_tests = std::env::var_os("OXIDE_TEST262_RUNNING_LOG").is_some(); let heartbeat_path: Option = std::env::var_os("OXIDE_TEST262_HEARTBEAT").map(PathBuf::from); - test262_info!("running on {} worker thread(s)", workers); - eprintln!("running on {workers} worker thread(s)"); - let skip_until = std::env::var("OXIDE_SKIP_UNTIL") .ok() .and_then(|s| s.parse::().ok()) @@ -173,7 +175,7 @@ fn run_tests() -> bool { let is_chunk_child = std::env::var_os("OXIDE_TEST262_CHILD_CHUNK").is_some(); let allow_fail_exit = std::env::var_os("OXIDE_TEST262_ALLOW_FAIL_EXIT").is_some(); - if config.supervise && !is_chunk_child && filter.is_none() { + if config.supervised(is_chunk_child) { test262_info!("supervised mode enabled"); eprintln!("supervised mode enabled: child-window execution with per-test timeout + auto-resume"); return run_supervised(&args, skip_until, end_index, config.no_skip, &paths); @@ -186,6 +188,9 @@ fn run_tests() -> bool { return run_chunked(&args, skip_until, end_index, chunk_size); } } + // 并行模式专属:监督/分块模式在此前已返回,不派生 worker 线程。 + test262_info!("running on {} worker thread(s)", workers); + eprintln!("running on {workers} worker thread(s)"); test262_info!("kernel reset batch: {} test(s)", kernel_batch); eprintln!("kernel reset batch: {kernel_batch} test(s)"); let cursor = AtomicUsize::new(skip_until); diff --git a/crates/oxide_test262/src/supervise.rs b/crates/oxide_test262/src/supervise.rs index afbf9600..0ac8f18b 100644 --- a/crates/oxide_test262/src/supervise.rs +++ b/crates/oxide_test262/src/supervise.rs @@ -248,11 +248,12 @@ pub(crate) fn run_supervised( id += 1; } + // 缺省 16:全量复测验证过的安全并发;未经复测不得抬高。 let supervisors = std::env::var("OXIDE_TEST262_SUPERVISORS") .ok() .and_then(|s| s.parse::().ok()) .filter(|&n| n > 0) - .unwrap_or_else(|| std::thread::available_parallelism().map(|n| n.get()).unwrap_or(4)) + .unwrap_or(16) .min(windows.len().max(1)); eprintln!(