diff --git a/Cargo.lock b/Cargo.lock index b83a06a2336..5f4a7597342 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -573,6 +573,7 @@ dependencies = [ "base64", "boa_engine", "boa_gc", + "bus", "bytemuck", "either", "futures", @@ -607,9 +608,7 @@ version = "1.0.0-dev" dependencies = [ "bitflags", "boa_engine", - "boa_gc", "boa_runtime", - "bus", "clap", "color-eyre", "colored", diff --git a/README.md b/README.md index 6b0b6d53be0..28bad44f65f 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,7 @@ Options: --flowgraph [] Generate instruction flowgraph. Default is Graphviz [possible values: graphviz, mermaid] --flowgraph-direction Specifies the direction of the flowgraph. Default is top-top-bottom [possible values: top-to-bottom, bottom-to-top, left-to-right, right-to-left] --debug-object Inject debugging object `$boa` + --test262-object Inject the test262 host object `$262` -m, --module Treats the input files as modules -r, --root Root path from where the module resolver will try to load the modules [default: .] -h, --help Print help (see more with '--help') diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 80a153a00bb..99c9e552083 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -15,7 +15,7 @@ rust-version.workspace = true boa_engine = { workspace = true, features = ["deser", "float16", "flowgraph", "temporal", "trace", "xsum"] } boa_parser.workspace = true boa_gc.workspace = true -boa_runtime.workspace = true +boa_runtime = { workspace = true, features = ["test262"] } rustyline = { workspace = true, features = ["derive", "with-file-history"] } clap = { workspace = true, features = ["derive"] } serde_json.workspace = true @@ -33,6 +33,7 @@ rustls.workspace = true [features] default = [ "boa_engine/annex-b", + "boa_runtime/annex-b", "boa_engine/experimental", "boa_engine/intl_bundled", "boa_engine/native-backtrace", diff --git a/cli/README.md b/cli/README.md index 9c65ddd73a5..873be3832cd 100644 --- a/cli/README.md +++ b/cli/README.md @@ -58,6 +58,7 @@ Options: --flowgraph [] Generate instruction flowgraph. Default is Graphviz [possible values: graphviz, mermaid] --flowgraph-direction Specifies the direction of the flowgraph. Default is top-top-bottom [possible values: top-to-bottom, bottom-to-top, left-to-right, right-to-left] --debug-object Inject debugging object `$boa` + --test262-object Inject the test262 host object `$262` -m, --module Treats the input files as modules -r, --root Root path from where the module resolver will try to load the modules [default: .] -e, --expression Execute a JavaScript expression then exit diff --git a/cli/src/main.rs b/cli/src/main.rs index abb0418a764..30902f1ae7f 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -153,6 +153,14 @@ struct Opt { #[arg(long)] debug_object: bool, + /// Inject the test262 host object `$262`. + #[arg(long)] + test262_object: bool, + + /// Disallow the main thread from blocking (e.g. `Atomics.wait`). + #[arg(long)] + no_can_block: bool, + /// Treats the input files as modules. #[arg(long, short = 'm', group = "mod")] module: bool, @@ -285,13 +293,18 @@ impl Drop for Counters { /// /// Returns a error of type String with a error message, /// if the source has a syntax or parsing error. -fn dump(src: Source<'_, R>, args: &Opt, context: &mut Context) -> Result<()> { +fn dump( + src: Source<'_, R>, + args: &Opt, + is_module: bool, + context: &mut Context, +) -> Result<()> { if let Some(arg) = args.dump_ast { let mut counters = Counters::new(args.time); let arg = arg.unwrap_or_default(); let mut parser = boa_parser::Parser::new(src); let dump = - if args.module { + if is_module { let scope = context.realm().scope().clone(); let module = { let _timer = counters.new_timer("Parsing"); @@ -386,7 +399,7 @@ fn evaluate_expr( printer: &SharedExternalPrinterLogger, ) -> Result<()> { if args.has_dump_flag() { - dump(Source::from_bytes(line), args, context)?; + dump(Source::from_bytes(line), args, args.module, context)?; } else if let Some(flowgraph) = args.flowgraph { match generate_flowgraph( context, @@ -433,8 +446,11 @@ fn evaluate_file( loader: &SimpleModuleLoader, printer: &SharedExternalPrinterLogger, ) -> Result<()> { + // Treat files with .mjs extension automatically as modules. + let is_module = args.module || file.extension().is_some_and(|ext| ext == "mjs"); + if args.has_dump_flag() { - return dump(Source::from_filepath(file)?, args, context); + return dump(Source::from_filepath(file)?, args, is_module, context); } if let Some(flowgraph) = args.flowgraph { @@ -450,7 +466,7 @@ fn evaluate_file( return Ok(()); } - if args.module { + if is_module { let source = Source::from_filepath(file)?; let mut counters = Counters::new(args.time); let module = { @@ -546,6 +562,7 @@ fn main() -> Result<()> { let context = &mut ContextBuilder::new() .job_executor(executor.clone()) .module_loader(loader.clone()) + .can_block(!args.no_can_block) .build() .map_err(|e| eyre!(e.to_string()))?; @@ -562,6 +579,20 @@ fn main() -> Result<()> { init_boa_debug_object(context); } + if args.test262_object { + boa_runtime::test262::register_js262( + boa_runtime::test262::WorkerHandles::new(), + true, // register `console` in $262.agent worker threads + context, + ); + + // Add print() that test262 uses to report errors and async success. + // boa_tester handles it internally, but CLI should just print messages. + context + .eval(Source::from_bytes("var print = console.log.bind(console);")) + .expect("failed to define print"); + } + // Configure optimizer options let mut optimizer_options = OptimizerOptions::empty(); optimizer_options.set(OptimizerOptions::STATISTICS, args.optimizer_statistics); diff --git a/core/runtime/Cargo.toml b/core/runtime/Cargo.toml index dd75e661be8..a2f45b78f04 100644 --- a/core/runtime/Cargo.toml +++ b/core/runtime/Cargo.toml @@ -14,6 +14,7 @@ rust-version.workspace = true boa_engine.workspace = true base64.workspace = true boa_gc.workspace = true +bus = { workspace = true, optional = true } bytemuck.workspace = true either = { workspace = true, optional = true } futures = "0.3.32" @@ -57,3 +58,5 @@ fetch = [ ] reqwest-blocking = ["dep:reqwest", "reqwest/blocking"] process = [] +annex-b = ["boa_engine/annex-b"] +test262 = ["dep:bus"] diff --git a/core/runtime/src/lib.rs b/core/runtime/src/lib.rs index 9e8b22d7617..c2e6e24968c 100644 --- a/core/runtime/src/lib.rs +++ b/core/runtime/src/lib.rs @@ -123,6 +123,9 @@ pub mod microtask; #[cfg(feature = "process")] pub mod process; pub mod store; +/// Support for the `$262` test262 harness object. +#[cfg(feature = "test262")] +pub mod test262; pub mod text; #[cfg(feature = "url")] pub mod url; diff --git a/tests/tester/src/exec/js262.rs b/core/runtime/src/test262.rs similarity index 89% rename from tests/tester/src/exec/js262.rs rename to core/runtime/src/test262.rs index 0edf3f9379e..2800ed290b0 100644 --- a/tests/tester/src/exec/js262.rs +++ b/core/runtime/src/test262.rs @@ -1,9 +1,12 @@ use std::{ cell::RefCell, rc::Rc, - sync::mpsc::{self, Sender}, + sync::{ + OnceLock, + mpsc::{self, Sender}, + }, thread::JoinHandle, - time::Duration, + time::{Duration, Instant}, }; #[cfg(feature = "annex-b")] @@ -18,25 +21,42 @@ use boa_engine::{ }; use bus::BusReader; -use crate::START; +/// Monotonic clock for `$262.agent.monotonicNow()`, initialized by `register_js262()`. +static START: OnceLock = OnceLock::new(); -pub(super) enum WorkerResult { +/// Result of a worker thread execution. +#[derive(Debug)] +pub enum WorkerResult { + /// The worker completed successfully. Ok, + /// The worker returned an error. Err(String), + /// The worker panicked. Panic(String), } -pub(super) type WorkerHandle = JoinHandle>; +type WorkerHandle = JoinHandle>; +/// Handles for worker threads spawned by `$262.agent.start()`. #[derive(Debug, Clone)] -pub(super) struct WorkerHandles(Rc>>); +pub struct WorkerHandles(Rc>>); + +impl Default for WorkerHandles { + fn default() -> Self { + Self::new() + } +} impl WorkerHandles { - pub(super) fn new() -> Self { + /// Creates a new empty set of worker handles. + #[must_use] + pub fn new() -> Self { Self(Rc::default()) } - pub(super) fn join_all(&mut self) -> Vec { + /// Joins all worker threads and returns their results. + #[allow(clippy::print_stderr)] + pub fn join_all(&mut self) -> Vec { let handles = std::mem::take(&mut *self.0.borrow_mut()); handles @@ -70,12 +90,13 @@ impl Drop for WorkerHandles { } } -/// Creates the object $262 in the context. -pub(super) fn register_js262( - handles: WorkerHandles, - console: bool, - context: &mut Context, -) -> JsObject { +/// Creates the object `$262` in the context. +/// +/// # Panics +/// +/// Panics if any of the expected global properties cannot be defined. +pub fn register_js262(handles: WorkerHandles, console: bool, context: &mut Context) -> JsObject { + START.get_or_init(Instant::now); let global_obj = context.global_object(); let agent = agent_obj(handles, console, context); @@ -205,6 +226,7 @@ fn monotonic_now(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult Js register_js262_worker(rx, tx, context); if console { - let console = boa_runtime::Console::init(context); + let console = crate::Console::init(context); context - .register_global_property( - boa_runtime::Console::NAME, - console, - Attribute::all(), - ) + .register_global_property(crate::Console::NAME, console, Attribute::all()) .expect("the console builtin shouldn't exist"); } diff --git a/tests/tester/Cargo.toml b/tests/tester/Cargo.toml index bfe03d356c4..f1fe5cad1bf 100644 --- a/tests/tester/Cargo.toml +++ b/tests/tester/Cargo.toml @@ -13,8 +13,7 @@ rust-version.workspace = true [dependencies] boa_engine = { workspace = true, features = ["float16"] } -boa_runtime.workspace = true -boa_gc.workspace = true +boa_runtime = { workspace = true, features = ["test262"] } clap = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] } serde_yaml = "0.9.34" # TODO: Track https://github.com/saphyr-rs/saphyr. @@ -28,11 +27,10 @@ color-eyre.workspace = true phf = { workspace = true, features = ["macros"] } comfy-table.workspace = true serde_repr.workspace = true -bus.workspace = true cow-utils.workspace = true [features] -annex-b = ["boa_engine/annex-b"] +annex-b = ["boa_engine/annex-b", "boa_runtime/annex-b"] default = ["boa_engine/intl_bundled", "boa_engine/experimental", "annex-b"] [lints] diff --git a/tests/tester/src/exec/mod.rs b/tests/tester/src/exec/mod.rs index 602e3d62e68..8f0072cf83b 100644 --- a/tests/tester/src/exec/mod.rs +++ b/tests/tester/src/exec/mod.rs @@ -1,6 +1,6 @@ //! Execution module for the test runner. -mod js262; +use boa_runtime::test262 as js262; use crate::{ Harness, Outcome, Phase, SpecEdition, Statistics, SuiteResult, Test, TestFlags, @@ -23,7 +23,7 @@ use rayon::prelude::*; use rustc_hash::FxHashSet; use std::{cell::RefCell, eprintln, path::Path, rc::Rc}; -use self::js262::WorkerHandles; +use js262::WorkerHandles; impl TestSuite { /// Runs the test suite. diff --git a/tests/tester/src/main.rs b/tests/tester/src/main.rs index 805903733e7..c630674723e 100644 --- a/tests/tester/src/main.rs +++ b/tests/tester/src/main.rs @@ -15,8 +15,6 @@ use std::{ ops::{Add, AddAssign}, path::{Path, PathBuf}, process::Command, - sync::OnceLock, - time::Instant, }; use bitflags::bitflags; @@ -46,8 +44,6 @@ mod exec; mod read; mod results; -static START: OnceLock = OnceLock::new(); - /// Structure that contains the configuration of the tester. #[derive(Debug, Deserialize)] struct Config { @@ -188,11 +184,6 @@ const DEFAULT_TEST262_DIRECTORY: &str = "test262"; fn main() -> Result<()> { color_eyre::install()?; - // initializes the monotonic clock. - START - .set(Instant::now()) - .map_err(|_| eyre!("could not initialize the monotonic clock"))?; - match Cli::parse() { Cli::Run { verbose,