Skip to content

Commit b895f74

Browse files
authored
Support running test262 using boa cli (#5234)
This Pull Request fixes/closes #5054 It changes the following: - Move `tests/tester/src/exec/js262.rs` to `core/runtime/src/test262.rs` so it can be reused by both cli and boa_tester - Add `--test262-object` flag to CLI - Add `print()` to CLI with `--test262-object` - Enable `[[CanBlock]]` by default in CLI to match boa_tester as well as d8/jsc/sm shells default behavior - Add `--no-can-block` flag to CLI to support Atomics CanBlockIsFalse tests (same flag name as d8 has) - Auto-detect .mjs files as modules in CLI (a small optional quality-of-life improvement; matches behavior of most other major JS engine shells) These changes make it possible to reproduce boa_tester's test262 results using external test262 harnesses with boa cli binary.
1 parent 333f6cb commit b895f74

11 files changed

Lines changed: 85 additions & 47 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ Options:
164164
--flowgraph [<FORMAT>] Generate instruction flowgraph. Default is Graphviz [possible values: graphviz, mermaid]
165165
--flowgraph-direction <FORMAT> 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]
166166
--debug-object Inject debugging object `$boa`
167+
--test262-object Inject the test262 host object `$262`
167168
-m, --module Treats the input files as modules
168169
-r, --root <ROOT> Root path from where the module resolver will try to load the modules [default: .]
169170
-h, --help Print help (see more with '--help')

cli/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ rust-version.workspace = true
1515
boa_engine = { workspace = true, features = ["deser", "float16", "flowgraph", "temporal", "trace", "xsum"] }
1616
boa_parser.workspace = true
1717
boa_gc.workspace = true
18-
boa_runtime.workspace = true
18+
boa_runtime = { workspace = true, features = ["test262"] }
1919
rustyline = { workspace = true, features = ["derive", "with-file-history"] }
2020
clap = { workspace = true, features = ["derive"] }
2121
serde_json.workspace = true
@@ -34,6 +34,7 @@ rustls.workspace = true
3434
[features]
3535
default = [
3636
"boa_engine/annex-b",
37+
"boa_runtime/annex-b",
3738
"boa_engine/experimental",
3839
"boa_engine/intl_bundled",
3940
"boa_engine/native-backtrace",

cli/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ Options:
5858
--flowgraph [<FORMAT>] Generate instruction flowgraph. Default is Graphviz [possible values: graphviz, mermaid]
5959
--flowgraph-direction <FORMAT> 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]
6060
--debug-object Inject debugging object `$boa`
61+
--test262-object Inject the test262 host object `$262`
6162
-m, --module Treats the input files as modules
6263
-r, --root <ROOT> Root path from where the module resolver will try to load the modules [default: .]
6364
-e, --expression <EXPR> Execute a JavaScript expression then exit

cli/src/main.rs

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,14 @@ struct Opt {
153153
#[arg(long)]
154154
debug_object: bool,
155155

156+
/// Inject the test262 host object `$262`.
157+
#[arg(long)]
158+
test262_object: bool,
159+
160+
/// Disallow the main thread from blocking (e.g. `Atomics.wait`).
161+
#[arg(long)]
162+
no_can_block: bool,
163+
156164
/// Treats the input files as modules.
157165
#[arg(long, short = 'm', group = "mod")]
158166
module: bool,
@@ -285,13 +293,18 @@ impl Drop for Counters {
285293
///
286294
/// Returns a error of type String with a error message,
287295
/// if the source has a syntax or parsing error.
288-
fn dump<R: ReadChar>(src: Source<'_, R>, args: &Opt, context: &mut Context) -> Result<()> {
296+
fn dump<R: ReadChar>(
297+
src: Source<'_, R>,
298+
args: &Opt,
299+
is_module: bool,
300+
context: &mut Context,
301+
) -> Result<()> {
289302
if let Some(arg) = args.dump_ast {
290303
let mut counters = Counters::new(args.time);
291304
let arg = arg.unwrap_or_default();
292305
let mut parser = boa_parser::Parser::new(src);
293306
let dump =
294-
if args.module {
307+
if is_module {
295308
let scope = context.realm().scope().clone();
296309
let module = {
297310
let _timer = counters.new_timer("Parsing");
@@ -386,7 +399,7 @@ fn evaluate_expr(
386399
printer: &SharedExternalPrinterLogger,
387400
) -> Result<()> {
388401
if args.has_dump_flag() {
389-
dump(Source::from_bytes(line), args, context)?;
402+
dump(Source::from_bytes(line), args, args.module, context)?;
390403
} else if let Some(flowgraph) = args.flowgraph {
391404
match generate_flowgraph(
392405
context,
@@ -433,8 +446,11 @@ fn evaluate_file(
433446
loader: &SimpleModuleLoader,
434447
printer: &SharedExternalPrinterLogger,
435448
) -> Result<()> {
449+
// Treat files with .mjs extension automatically as modules.
450+
let is_module = args.module || file.extension().is_some_and(|ext| ext == "mjs");
451+
436452
if args.has_dump_flag() {
437-
return dump(Source::from_filepath(file)?, args, context);
453+
return dump(Source::from_filepath(file)?, args, is_module, context);
438454
}
439455

440456
if let Some(flowgraph) = args.flowgraph {
@@ -450,7 +466,7 @@ fn evaluate_file(
450466
return Ok(());
451467
}
452468

453-
if args.module {
469+
if is_module {
454470
let source = Source::from_filepath(file)?;
455471
let mut counters = Counters::new(args.time);
456472
let module = {
@@ -546,6 +562,7 @@ fn main() -> Result<()> {
546562
let context = &mut ContextBuilder::new()
547563
.job_executor(executor.clone())
548564
.module_loader(loader.clone())
565+
.can_block(!args.no_can_block)
549566
.build()
550567
.map_err(|e| eyre!(e.to_string()))?;
551568

@@ -562,6 +579,20 @@ fn main() -> Result<()> {
562579
init_boa_debug_object(context);
563580
}
564581

582+
if args.test262_object {
583+
boa_runtime::test262::register_js262(
584+
boa_runtime::test262::WorkerHandles::new(),
585+
true, // register `console` in $262.agent worker threads
586+
context,
587+
);
588+
589+
// Add print() that test262 uses to report errors and async success.
590+
// boa_tester handles it internally, but CLI should just print messages.
591+
context
592+
.eval(Source::from_bytes("var print = console.log.bind(console);"))
593+
.expect("failed to define print");
594+
}
595+
565596
// Configure optimizer options
566597
let mut optimizer_options = OptimizerOptions::empty();
567598
optimizer_options.set(OptimizerOptions::STATISTICS, args.optimizer_statistics);

core/runtime/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ rust-version.workspace = true
1414
boa_engine.workspace = true
1515
base64.workspace = true
1616
boa_gc.workspace = true
17+
bus = { workspace = true, optional = true }
1718
bytemuck.workspace = true
1819
either = { workspace = true, optional = true }
1920
futures = "0.3.32"
@@ -57,3 +58,5 @@ fetch = [
5758
]
5859
reqwest-blocking = ["dep:reqwest", "reqwest/blocking"]
5960
process = []
61+
annex-b = ["boa_engine/annex-b"]
62+
test262 = ["dep:bus"]

core/runtime/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ pub mod microtask;
123123
#[cfg(feature = "process")]
124124
pub mod process;
125125
pub mod store;
126+
/// Support for the `$262` test262 harness object.
127+
#[cfg(feature = "test262")]
128+
pub mod test262;
126129
pub mod text;
127130
#[cfg(feature = "url")]
128131
pub mod url;
Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,39 @@ use boa_engine::{
1818
};
1919
use bus::BusReader;
2020

21-
use crate::START;
22-
23-
pub(super) enum WorkerResult {
21+
/// Result of a worker thread execution.
22+
#[derive(Debug)]
23+
pub enum WorkerResult {
24+
/// The worker completed successfully.
2425
Ok,
26+
/// The worker returned an error.
2527
Err(String),
28+
/// The worker panicked.
2629
Panic(String),
2730
}
2831

29-
pub(super) type WorkerHandle = JoinHandle<Result<(), String>>;
32+
type WorkerHandle = JoinHandle<Result<(), String>>;
3033

34+
/// Handles for worker threads spawned by `$262.agent.start()`.
3135
#[derive(Debug, Clone)]
32-
pub(super) struct WorkerHandles(Rc<RefCell<Vec<WorkerHandle>>>);
36+
pub struct WorkerHandles(Rc<RefCell<Vec<WorkerHandle>>>);
37+
38+
impl Default for WorkerHandles {
39+
fn default() -> Self {
40+
Self::new()
41+
}
42+
}
3343

3444
impl WorkerHandles {
35-
pub(super) fn new() -> Self {
45+
/// Creates a new empty set of worker handles.
46+
#[must_use]
47+
pub fn new() -> Self {
3648
Self(Rc::default())
3749
}
3850

39-
pub(super) fn join_all(&mut self) -> Vec<WorkerResult> {
51+
/// Joins all worker threads and returns their results.
52+
#[allow(clippy::print_stderr)]
53+
pub fn join_all(&mut self) -> Vec<WorkerResult> {
4054
let handles = std::mem::take(&mut *self.0.borrow_mut());
4155

4256
handles
@@ -70,12 +84,12 @@ impl Drop for WorkerHandles {
7084
}
7185
}
7286

73-
/// Creates the object $262 in the context.
74-
pub(super) fn register_js262(
75-
handles: WorkerHandles,
76-
console: bool,
77-
context: &mut Context,
78-
) -> JsObject {
87+
/// Creates the object `$262` in the context.
88+
///
89+
/// # Panics
90+
///
91+
/// Panics if any of the expected global properties cannot be defined.
92+
pub fn register_js262(handles: WorkerHandles, console: bool, context: &mut Context) -> JsObject {
7993
let global_obj = context.global_object();
8094

8195
let agent = agent_obj(handles, console, context);
@@ -201,11 +215,11 @@ fn sleep(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsVal
201215

202216
/// The `$262.agent.monotonicNow()` function.
203217
#[allow(clippy::unnecessary_wraps)]
204-
fn monotonic_now(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {
205-
let clock = START
206-
.get()
207-
.ok_or_else(|| JsNativeError::typ().with_message("could not get the monotonic clock"))?;
208-
Ok(JsValue::from(clock.elapsed().as_millis() as f64))
218+
fn monotonic_now(_: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
219+
#[allow(clippy::cast_precision_loss)]
220+
Ok(JsValue::from(
221+
context.clock().now().millis_since_epoch() as f64
222+
))
209223
}
210224

211225
/// Initializes the `$262.agent` object in the main agent.
@@ -235,14 +249,10 @@ fn agent_obj(handles: WorkerHandles, console: bool, context: &mut Context) -> Js
235249
register_js262_worker(rx, tx, context);
236250

237251
if console {
238-
let console = boa_runtime::Console::init(context);
252+
let console = crate::Console::init(context);
239253

240254
context
241-
.register_global_property(
242-
boa_runtime::Console::NAME,
243-
console,
244-
Attribute::all(),
245-
)
255+
.register_global_property(crate::Console::NAME, console, Attribute::all())
246256
.expect("the console builtin shouldn't exist");
247257
}
248258

tests/tester/Cargo.toml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ rust-version.workspace = true
1313

1414
[dependencies]
1515
boa_engine = { workspace = true, features = ["float16"] }
16-
boa_runtime.workspace = true
17-
boa_gc.workspace = true
16+
boa_runtime = { workspace = true, features = ["test262"] }
1817
clap = { workspace = true, features = ["derive"] }
1918
serde = { workspace = true, features = ["derive"] }
2019
serde_yaml = "0.9.34" # TODO: Track https://github.com/saphyr-rs/saphyr.
@@ -28,11 +27,10 @@ color-eyre.workspace = true
2827
phf = { workspace = true, features = ["macros"] }
2928
comfy-table.workspace = true
3029
serde_repr.workspace = true
31-
bus.workspace = true
3230
cow-utils.workspace = true
3331

3432
[features]
35-
annex-b = ["boa_engine/annex-b"]
33+
annex-b = ["boa_engine/annex-b", "boa_runtime/annex-b"]
3634
default = ["boa_engine/intl_bundled", "boa_engine/experimental", "annex-b"]
3735

3836
[lints]

tests/tester/src/exec/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Execution module for the test runner.
22
3-
mod js262;
3+
use boa_runtime::test262 as js262;
44

55
use crate::{
66
Harness, Outcome, Phase, SpecEdition, Statistics, SuiteResult, Test, TestFlags,
@@ -23,7 +23,7 @@ use rayon::prelude::*;
2323
use rustc_hash::FxHashSet;
2424
use std::{cell::RefCell, eprintln, path::Path, rc::Rc};
2525

26-
use self::js262::WorkerHandles;
26+
use js262::WorkerHandles;
2727

2828
impl TestSuite {
2929
/// Runs the test suite.

0 commit comments

Comments
 (0)