fix: 修复同步调用参数限制与内建语义错误

This commit is contained in:
爱特每羊队
2026-06-23 14:38:58 +08:00
parent 933612c89d
commit 51ebcbdb9b
13 changed files with 675 additions and 189 deletions
+123 -19
View File
@@ -6,6 +6,7 @@ use oxide_vm::vm::Vm;
use serde::Deserialize;
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::time::Duration;
@@ -511,12 +512,89 @@ fn run_test_inner(
}
fn discover_tests(test262_root: &Path) -> Vec<PathBuf> {
WalkDir::new(test262_root)
let mut paths: Vec<PathBuf> = WalkDir::new(test262_root)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().is_some_and(|ext| ext == "js"))
.map(|e| e.path().to_path_buf())
.collect()
.collect();
paths.sort();
paths
}
fn parse_summary_count(stdout: &str, label: &str) -> Option<usize> {
stdout.lines().find_map(|line| {
let trimmed = line.trim_start();
let rest = trimmed.strip_prefix(label)?;
let value = rest.trim().split(' ').next()?;
value.parse::<usize>().ok()
})
}
fn run_chunked(args: &[String], skip_until: usize, end_index: usize, chunk_size: usize) -> bool {
let exe = match std::env::current_exe() {
Ok(path) => path,
Err(err) => {
eprintln!("failed to resolve current executable for chunked mode: {err}");
return false;
}
};
let mut aggregate_pass = 0usize;
let mut aggregate_fail = 0usize;
let mut aggregate_skip = 0usize;
let mut chunk_start = skip_until;
let mut chunk_id = 1usize;
while chunk_start < end_index {
let chunk_len = (end_index - chunk_start).min(chunk_size);
eprintln!("chunk {chunk_id}: tests [{chunk_start}, {})", chunk_start + chunk_len);
let output = match Command::new(&exe)
.args(args.iter().skip(1))
.env("OXIDE_SKIP_UNTIL", chunk_start.to_string())
.env("OXIDE_MAX_TESTS", chunk_len.to_string())
.env("OXIDE_TEST262_CHILD_CHUNK", "1")
.env("OXIDE_TEST262_ALLOW_FAIL_EXIT", "1")
.output()
{
Ok(output) => output,
Err(err) => {
eprintln!("failed to run chunk {chunk_id}: {err}");
return false;
}
};
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
print!("{stdout}");
eprint!("{stderr}");
if !output.status.success() {
eprintln!("chunk {chunk_id} crashed or aborted");
return false;
}
aggregate_pass += parse_summary_count(&stdout, "pass :").unwrap_or(0);
aggregate_fail += parse_summary_count(&stdout, "fail :").unwrap_or(0);
aggregate_skip += parse_summary_count(&stdout, "skip :").unwrap_or(0);
chunk_start += chunk_len;
chunk_id += 1;
}
let aggregate_total = aggregate_pass + aggregate_fail + aggregate_skip;
println!();
println!("═══════════════════════════════════════");
println!(" test262 chunked aggregate");
println!("═══════════════════════════════════════");
println!(" total : {}", aggregate_total);
println!(" pass : {}", aggregate_pass);
println!(" fail : {}", aggregate_fail);
println!(" skip : {}", aggregate_skip);
println!("═══════════════════════════════════════");
aggregate_fail == 0
}
/// Per-test pipeline shared by the serial and parallel execution paths:
@@ -716,11 +794,16 @@ fn run_tests() -> bool {
// own kernel + harness-source registry + prefix cache. Only `PathBuf` and
// `TestResult` (both `Send`) cross thread boundaries. Workers pull test
// indices from a shared atomic cursor for dynamic load balancing.
let default_workers = if config.no_skip {
4
} else {
std::thread::available_parallelism().map(|n| n.get()).unwrap_or(4)
};
let workers = std::env::var("OXIDE_TEST262_WORKERS")
.ok()
.and_then(|s| s.parse::<usize>().ok())
.filter(|&n| n > 0)
.unwrap_or_else(|| std::thread::available_parallelism().map(|n| n.get()).unwrap_or(4))
.unwrap_or(default_workers)
.min(total.max(1));
let log_running_tests = std::env::var_os("OXIDE_TEST262_RUNNING_LOG").is_some();
@@ -737,6 +820,25 @@ fn run_tests() -> bool {
.and_then(|n| skip_until.checked_add(n))
.map(|n| n.min(total))
.unwrap_or(total);
let kernel_batch = std::env::var("OXIDE_TEST262_KERNEL_BATCH")
.ok()
.and_then(|s| s.parse::<usize>().ok())
.filter(|&n| n > 0)
.unwrap_or(if config.no_skip { 1000 } else { 5000 });
let chunk_size = std::env::var("OXIDE_TEST262_CHUNK_SIZE")
.ok()
.and_then(|s| s.parse::<usize>().ok())
.filter(|&n| n > 0);
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 let Some(chunk_size) = chunk_size {
if !is_chunk_child && filter.is_none() {
eprintln!("chunked mode enabled: {chunk_size} test(s) per child process");
return run_chunked(&args, skip_until, end_index, chunk_size);
}
}
eprintln!("kernel reset batch: {kernel_batch} test(s)");
let cursor = AtomicUsize::new(skip_until);
let progress = AtomicUsize::new(skip_until);
let filter = &filter;
@@ -744,9 +846,10 @@ fn run_tests() -> bool {
let paths_ref = &paths;
let harness_cache = Arc::new(RwLock::new(HarnessPrefixCache::new()));
// Each worker returns its partial stats plus (index, result) pairs so the
// final results vector can be restored to discovery order for stable output.
let partials: Vec<(RunStats, Vec<(usize, TestResult)>)> = std::thread::scope(|scope| {
// Keep memory flat: workers return only aggregate stats. Retaining tens of
// thousands of `TestResult`s makes `--no-skip` runs accumulate path/error
// strings until the process OOMs near the end of the suite.
let partials: Vec<RunStats> = std::thread::scope(|scope| {
let handles: Vec<_> = (0..workers)
.map(|_| {
let cursor = &cursor;
@@ -757,10 +860,10 @@ fn run_tests() -> bool {
std::thread::Builder::new()
.stack_size(16 * 1024 * 1024)
.spawn_scoped(scope, move || {
let kernel = build_runner_kernel();
let mut kernel = build_runner_kernel();
let harness_sources = HARNESS.get_or_init(HarnessSources::new);
let mut stats = RunStats::default();
let mut out: Vec<(usize, TestResult)> = Vec::new();
let mut tests_since_kernel_reset = 0usize;
let tid = std::thread::current().id();
loop {
@@ -780,18 +883,22 @@ fn run_tests() -> bool {
let result =
process_path(&paths_ref[i], filter, no_skip, &kernel, harness_sources, &harness_cache);
stats.record(&result);
out.push((i, result));
tests_since_kernel_reset += 1;
let done = progress.fetch_add(1, Ordering::Relaxed) + 1;
if done % 500 == 0 {
kernel.sweep_runner_forges();
}
if tests_since_kernel_reset >= kernel_batch {
kernel = build_runner_kernel();
tests_since_kernel_reset = 0;
}
if done % 500 == 0 || done == total {
eprintln!(" progress: {done}/{total} ({}%)", done * 100 / total);
}
}
(stats, out)
stats
})
.expect("failed to spawn test262 worker thread")
})
@@ -803,26 +910,23 @@ fn run_tests() -> bool {
.collect()
});
// Reduce partial stats and restore discovery order for deterministic output.
// Reduce partial stats without materializing every test result in memory.
let mut stats = RunStats::default();
let mut indexed: Vec<(usize, TestResult)> = Vec::with_capacity(total);
for (partial_stats, partial_results) in partials {
for partial_stats in partials {
stats.merge(partial_stats);
indexed.extend(partial_results);
}
indexed.sort_by_key(|(i, _)| *i);
let results: Vec<TestResult> = indexed.into_iter().map(|(_, r)| r).collect();
eprintln!();
let ran = stats.pass + stats.fail;
let executed_total = end_index.saturating_sub(skip_until);
println!();
println!("═══════════════════════════════════════");
println!(" test262 results");
println!("═══════════════════════════════════════");
println!(" total : {}", results.len());
let total = results.len() as f64;
println!(" total : {}", executed_total);
let total = executed_total as f64;
println!(" pass : {} ({:.1}%)", stats.pass, stats.pass as f64 / total * 100.0);
println!(" fail : {} ({:.1}%)", stats.fail, stats.fail as f64 / total * 100.0);
println!(" skip : {} ({:.1}%)", stats.skip, stats.skip as f64 / total * 100.0);
@@ -842,7 +946,7 @@ fn run_tests() -> bool {
}
println!("═══════════════════════════════════════");
if stats.fail > 0 {
if stats.fail > 0 && !allow_fail_exit {
return false;
}
true