feat: works
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
use boa_engine::{
|
||||
builtins::JsArgs,
|
||||
object::{JsObject, ObjectInitializer},
|
||||
property::Attribute,
|
||||
Context, JsResult, JsValue,
|
||||
};
|
||||
|
||||
/// Initializes the object in the context.
|
||||
pub(super) fn init(context: &mut Context) -> JsObject {
|
||||
let global_obj = context.global_object().clone();
|
||||
|
||||
let obj = ObjectInitializer::new(context)
|
||||
.function(create_realm, "createRealm", 0)
|
||||
.function(detach_array_buffer, "detachArrayBuffer", 2)
|
||||
.function(eval_script, "evalScript", 1)
|
||||
.function(gc, "gc", 0)
|
||||
.property("global", global_obj, Attribute::default())
|
||||
// .property("agent", agent, Attribute::default())
|
||||
.build();
|
||||
|
||||
context.register_global_property("$262", obj.clone(), Attribute::empty());
|
||||
|
||||
obj
|
||||
}
|
||||
|
||||
/// The `$262.createRealm()` function.
|
||||
///
|
||||
/// Creates a new ECMAScript Realm, defines this API on the new realm's global object, and
|
||||
/// returns the `$262` property of the new realm's global object.
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn create_realm(_this: &JsValue, _: &[JsValue], _context: &mut Context) -> JsResult<JsValue> {
|
||||
let mut context = Context::default();
|
||||
|
||||
// add the $262 object.
|
||||
let js_262 = init(&mut context);
|
||||
|
||||
Ok(JsValue::new(js_262))
|
||||
}
|
||||
|
||||
/// The `$262.detachArrayBuffer()` function.
|
||||
///
|
||||
/// Implements the `DetachArrayBuffer` abstract operation.
|
||||
fn detach_array_buffer(
|
||||
_this: &JsValue,
|
||||
args: &[JsValue],
|
||||
context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
#[inline]
|
||||
fn type_err(context: &mut Context) -> JsValue {
|
||||
context.construct_type_error("The provided object was not an ArrayBuffer")
|
||||
}
|
||||
|
||||
let array_buffer = args
|
||||
.get(0)
|
||||
.and_then(JsValue::as_object)
|
||||
.ok_or_else(|| type_err(context))?;
|
||||
let mut array_buffer = array_buffer.borrow_mut();
|
||||
let array_buffer = array_buffer
|
||||
.as_array_buffer_mut()
|
||||
.ok_or_else(|| type_err(context))?;
|
||||
|
||||
// 1. Assert: IsSharedArrayBuffer(arrayBuffer) is false. TODO
|
||||
// 2. If key is not present, set key to undefined.
|
||||
let key = args.get_or_undefined(1);
|
||||
|
||||
// 3. If SameValue(arrayBuffer.[[ArrayBufferDetachKey]], key) is false, throw a TypeError exception.
|
||||
if !JsValue::same_value(&array_buffer.array_buffer_detach_key, key) {
|
||||
return context.throw_type_error("Cannot detach array buffer with different key");
|
||||
}
|
||||
|
||||
// 4. Set arrayBuffer.[[ArrayBufferData]] to null.
|
||||
array_buffer.array_buffer_data = None;
|
||||
|
||||
// 5. Set arrayBuffer.[[ArrayBufferByteLength]] to 0.
|
||||
array_buffer.array_buffer_byte_length = 0;
|
||||
|
||||
// 6. Return NormalCompletion(null).
|
||||
Ok(JsValue::null())
|
||||
}
|
||||
|
||||
/// The `$262.evalScript()` function.
|
||||
///
|
||||
/// Accepts a string value as its first argument and executes it as an ECMAScript script.
|
||||
fn eval_script(_this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
|
||||
if let Some(source_text) = args.get(0).and_then(JsValue::as_string) {
|
||||
match context.parse(source_text.as_str()) {
|
||||
// TODO: check strict
|
||||
Err(e) => context.throw_type_error(format!("Uncaught Syntax Error: {e}")),
|
||||
// Calling eval here parses the code a second time.
|
||||
// TODO: We can fix this after we have have defined the public api for the vm executer.
|
||||
Ok(_) => context.eval(source_text.as_str()),
|
||||
}
|
||||
} else {
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
}
|
||||
|
||||
/// The `$262.gc()` function.
|
||||
///
|
||||
/// Wraps the host's garbage collection invocation mechanism, if such a capability exists.
|
||||
/// Must throw an exception if no capability exists. This is necessary for testing the
|
||||
/// semantics of any feature that relies on garbage collection, e.g. the `WeakRef` API.
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn gc(_this: &JsValue, _: &[JsValue], _context: &mut Context) -> JsResult<JsValue> {
|
||||
boa_gc::force_collect();
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
402
__wasm/wit-bindgen-sample/engine/boa/boa_tester/src/exec/mod.rs
Normal file
402
__wasm/wit-bindgen-sample/engine/boa/boa_tester/src/exec/mod.rs
Normal file
@@ -0,0 +1,402 @@
|
||||
//! Execution module for the test runner.
|
||||
|
||||
mod js262;
|
||||
|
||||
use super::{
|
||||
Harness, Outcome, Phase, SuiteResult, Test, TestFlags, TestOutcomeResult, TestResult,
|
||||
TestSuite, IGNORED,
|
||||
};
|
||||
use boa_engine::{
|
||||
builtins::JsArgs, object::FunctionBuilder, property::Attribute, syntax::Parser, Context,
|
||||
JsResult, JsValue,
|
||||
};
|
||||
use boa_gc::{Cell, Finalize, Gc, Trace};
|
||||
use colored::Colorize;
|
||||
use rayon::prelude::*;
|
||||
use std::panic;
|
||||
|
||||
impl TestSuite {
|
||||
/// Runs the test suite.
|
||||
pub(crate) fn run(&self, harness: &Harness, verbose: u8, parallel: bool) -> SuiteResult {
|
||||
if verbose != 0 {
|
||||
println!("Suite {}:", self.name);
|
||||
}
|
||||
|
||||
let suites: Vec<_> = if parallel {
|
||||
self.suites
|
||||
.par_iter()
|
||||
.map(|suite| suite.run(harness, verbose, parallel))
|
||||
.collect()
|
||||
} else {
|
||||
self.suites
|
||||
.iter()
|
||||
.map(|suite| suite.run(harness, verbose, parallel))
|
||||
.collect()
|
||||
};
|
||||
|
||||
let tests: Vec<_> = if parallel {
|
||||
self.tests
|
||||
.par_iter()
|
||||
.flat_map(|test| test.run(harness, verbose))
|
||||
.collect()
|
||||
} else {
|
||||
self.tests
|
||||
.iter()
|
||||
.flat_map(|test| test.run(harness, verbose))
|
||||
.collect()
|
||||
};
|
||||
|
||||
let mut features = Vec::new();
|
||||
for test_iter in self.tests.iter() {
|
||||
for feature_iter in test_iter.features.iter() {
|
||||
features.push(feature_iter.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if verbose != 0 {
|
||||
println!();
|
||||
}
|
||||
|
||||
// Count passed tests
|
||||
let mut passed = 0;
|
||||
let mut ignored = 0;
|
||||
let mut panic = 0;
|
||||
for test in &tests {
|
||||
match test.result {
|
||||
TestOutcomeResult::Passed => passed += 1,
|
||||
TestOutcomeResult::Ignored => ignored += 1,
|
||||
TestOutcomeResult::Panic => panic += 1,
|
||||
TestOutcomeResult::Failed => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Count total tests
|
||||
let mut total = tests.len();
|
||||
for suite in &suites {
|
||||
total += suite.total;
|
||||
passed += suite.passed;
|
||||
ignored += suite.ignored;
|
||||
panic += suite.panic;
|
||||
features.append(&mut suite.features.clone());
|
||||
}
|
||||
|
||||
if verbose != 0 {
|
||||
println!(
|
||||
"Suite {} results: total: {total}, passed: {}, ignored: {}, failed: {} (panics: \
|
||||
{}{}), conformance: {:.2}%",
|
||||
self.name,
|
||||
passed.to_string().green(),
|
||||
ignored.to_string().yellow(),
|
||||
(total - passed - ignored).to_string().red(),
|
||||
if panic == 0 {
|
||||
"0".normal()
|
||||
} else {
|
||||
panic.to_string().red()
|
||||
},
|
||||
if panic == 0 { "" } else { " ⚠" }.red(),
|
||||
(passed as f64 / total as f64) * 100.0
|
||||
);
|
||||
}
|
||||
|
||||
SuiteResult {
|
||||
name: self.name.clone(),
|
||||
total,
|
||||
passed,
|
||||
ignored,
|
||||
panic,
|
||||
suites,
|
||||
tests,
|
||||
features,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Test {
|
||||
/// Runs the test.
|
||||
pub(crate) fn run(&self, harness: &Harness, verbose: u8) -> Vec<TestResult> {
|
||||
let mut results = Vec::new();
|
||||
if self.flags.contains(TestFlags::STRICT) && !self.flags.contains(TestFlags::RAW) {
|
||||
results.push(self.run_once(harness, true, verbose));
|
||||
}
|
||||
|
||||
if self.flags.contains(TestFlags::NO_STRICT) || self.flags.contains(TestFlags::RAW) {
|
||||
results.push(self.run_once(harness, false, verbose));
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
/// Runs the test once, in strict or non-strict mode
|
||||
fn run_once(&self, harness: &Harness, strict: bool, verbose: u8) -> TestResult {
|
||||
if verbose > 1 {
|
||||
println!(
|
||||
"`{}`{}: starting",
|
||||
self.name,
|
||||
if strict { " (strict mode)" } else { "" }
|
||||
);
|
||||
}
|
||||
|
||||
let test_content = if strict {
|
||||
format!("\"use strict\";\n{}", self.content)
|
||||
} else {
|
||||
self.content.to_string()
|
||||
};
|
||||
|
||||
let (result, result_text) = if !IGNORED.contains_any_flag(self.flags)
|
||||
&& !IGNORED.contains_test(&self.name)
|
||||
&& !IGNORED.contains_any_feature(&self.features)
|
||||
&& (matches!(self.expected_outcome, Outcome::Positive)
|
||||
|| matches!(
|
||||
self.expected_outcome,
|
||||
Outcome::Negative {
|
||||
phase: Phase::Parse,
|
||||
error_type: _,
|
||||
}
|
||||
)
|
||||
|| matches!(
|
||||
self.expected_outcome,
|
||||
Outcome::Negative {
|
||||
phase: Phase::Early,
|
||||
error_type: _,
|
||||
}
|
||||
)
|
||||
|| matches!(
|
||||
self.expected_outcome,
|
||||
Outcome::Negative {
|
||||
phase: Phase::Runtime,
|
||||
error_type: _,
|
||||
}
|
||||
)) {
|
||||
let res = panic::catch_unwind(|| match self.expected_outcome {
|
||||
Outcome::Positive => {
|
||||
let mut context = Context::default();
|
||||
|
||||
let callback_obj = CallbackObject::default();
|
||||
// TODO: timeout
|
||||
match self.set_up_env(harness, &mut context, callback_obj.clone()) {
|
||||
Ok(_) => {
|
||||
let res = context.eval(&test_content);
|
||||
|
||||
let passed = res.is_ok()
|
||||
&& matches!(*callback_obj.result.borrow(), Some(true) | None);
|
||||
let text = match res {
|
||||
Ok(val) => val.display().to_string(),
|
||||
Err(e) => format!("Uncaught {}", e.display()),
|
||||
};
|
||||
|
||||
(passed, text)
|
||||
}
|
||||
Err(e) => (false, e),
|
||||
}
|
||||
}
|
||||
Outcome::Negative {
|
||||
phase: Phase::Parse | Phase::Early,
|
||||
ref error_type,
|
||||
} => {
|
||||
assert_eq!(
|
||||
error_type.as_ref(),
|
||||
"SyntaxError",
|
||||
"non-SyntaxError parsing/early error found in {}",
|
||||
self.name
|
||||
);
|
||||
|
||||
let mut context = Context::default();
|
||||
match context.parse(&test_content) {
|
||||
Ok(statement_list) => match context.compile(&statement_list) {
|
||||
Ok(_) => (false, "StatementList compilation should fail".to_owned()),
|
||||
Err(e) => (true, format!("Uncaught {e:?}")),
|
||||
},
|
||||
Err(e) => (true, format!("Uncaught {e}")),
|
||||
}
|
||||
}
|
||||
Outcome::Negative {
|
||||
phase: Phase::Resolution,
|
||||
error_type: _,
|
||||
} => todo!("check module resolution errors"),
|
||||
Outcome::Negative {
|
||||
phase: Phase::Runtime,
|
||||
ref error_type,
|
||||
} => {
|
||||
let mut context = Context::default();
|
||||
if let Err(e) = Parser::new(test_content.as_bytes()).parse_all(&mut context) {
|
||||
(false, format!("Uncaught {e}"))
|
||||
} else {
|
||||
// TODO: timeout
|
||||
match self.set_up_env(harness, &mut context, CallbackObject::default()) {
|
||||
Ok(_) => match context.eval(&test_content) {
|
||||
Ok(res) => (false, res.display().to_string()),
|
||||
Err(e) => {
|
||||
let passed = e
|
||||
.display()
|
||||
.internals(true)
|
||||
.to_string()
|
||||
.contains(error_type.as_ref());
|
||||
|
||||
(passed, format!("Uncaught {}", e.display()))
|
||||
}
|
||||
},
|
||||
Err(e) => (false, e),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let result = res.map_or_else(
|
||||
|_| {
|
||||
eprintln!("last panic was on test \"{}\"", self.name);
|
||||
(TestOutcomeResult::Panic, String::new())
|
||||
},
|
||||
|(res, text)| {
|
||||
if res {
|
||||
(TestOutcomeResult::Passed, text)
|
||||
} else {
|
||||
(TestOutcomeResult::Failed, text)
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if verbose > 1 {
|
||||
println!(
|
||||
"`{}`{}: {}",
|
||||
self.name,
|
||||
if strict { " (strict mode)" } else { "" },
|
||||
if matches!(result, (TestOutcomeResult::Passed, _)) {
|
||||
"Passed".green()
|
||||
} else if matches!(result, (TestOutcomeResult::Failed, _)) {
|
||||
"Failed".red()
|
||||
} else {
|
||||
"⚠ Panic ⚠".red()
|
||||
}
|
||||
);
|
||||
} else {
|
||||
print!(
|
||||
"{}",
|
||||
if matches!(result, (TestOutcomeResult::Passed, _)) {
|
||||
".".green()
|
||||
} else {
|
||||
".".red()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
result
|
||||
} else {
|
||||
if verbose > 1 {
|
||||
println!(
|
||||
"`{}`{}: {}",
|
||||
self.name,
|
||||
if strict { " (strict mode)" } else { "" },
|
||||
"Ignored".yellow()
|
||||
);
|
||||
} else {
|
||||
print!("{}", ".".yellow());
|
||||
}
|
||||
(TestOutcomeResult::Ignored, String::new())
|
||||
};
|
||||
|
||||
if verbose > 2 {
|
||||
println!(
|
||||
"`{}`{}: result text",
|
||||
self.name,
|
||||
if strict { " (strict mode)" } else { "" },
|
||||
);
|
||||
println!("{result_text}");
|
||||
println!();
|
||||
}
|
||||
|
||||
TestResult {
|
||||
name: self.name.clone(),
|
||||
strict,
|
||||
result,
|
||||
result_text: result_text.into_boxed_str(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the environment up to run the test.
|
||||
fn set_up_env(
|
||||
&self,
|
||||
harness: &Harness,
|
||||
context: &mut Context,
|
||||
callback_obj: CallbackObject,
|
||||
) -> Result<(), String> {
|
||||
// Register the print() function.
|
||||
Self::register_print_fn(context, callback_obj);
|
||||
|
||||
// add the $262 object.
|
||||
let _js262 = js262::init(context);
|
||||
|
||||
if self.flags.contains(TestFlags::RAW) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
context
|
||||
.eval(harness.assert.as_ref())
|
||||
.map_err(|e| format!("could not run assert.js:\n{}", e.display()))?;
|
||||
context
|
||||
.eval(harness.sta.as_ref())
|
||||
.map_err(|e| format!("could not run sta.js:\n{}", e.display()))?;
|
||||
|
||||
if self.flags.contains(TestFlags::ASYNC) {
|
||||
context
|
||||
.eval(harness.doneprint_handle.as_ref())
|
||||
.map_err(|e| format!("could not run doneprintHandle.js:\n{}", e.display()))?;
|
||||
}
|
||||
|
||||
for include in self.includes.iter() {
|
||||
context
|
||||
.eval(
|
||||
&harness
|
||||
.includes
|
||||
.get(include)
|
||||
.ok_or_else(|| format!("could not find the {include} include file."))?
|
||||
.as_ref(),
|
||||
)
|
||||
.map_err(|e| {
|
||||
format!(
|
||||
"could not run the {include} include file:\nUncaught {}",
|
||||
e.display()
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Registers the print function in the context.
|
||||
fn register_print_fn(context: &mut Context, callback_object: CallbackObject) {
|
||||
// We use `FunctionBuilder` to define a closure with additional captures.
|
||||
let js_function =
|
||||
FunctionBuilder::closure_with_captures(context, test262_print, callback_object)
|
||||
.name("print")
|
||||
.length(1)
|
||||
.build();
|
||||
|
||||
context.register_global_property(
|
||||
"print",
|
||||
js_function,
|
||||
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Object which includes the result of the async operation.
|
||||
#[derive(Debug, Clone, Default, Trace, Finalize)]
|
||||
struct CallbackObject {
|
||||
result: Gc<Cell<Option<bool>>>,
|
||||
}
|
||||
|
||||
/// `print()` function required by the test262 suite.
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn test262_print(
|
||||
_this: &JsValue,
|
||||
args: &[JsValue],
|
||||
captures: &mut CallbackObject,
|
||||
_context: &mut Context,
|
||||
) -> JsResult<JsValue> {
|
||||
if let Some(message) = args.get_or_undefined(0).as_string() {
|
||||
*captures.result.borrow_mut() = Some(message.as_str() == "Test262:AsyncTestComplete");
|
||||
} else {
|
||||
*captures.result.borrow_mut() = Some(false);
|
||||
}
|
||||
Ok(JsValue::undefined())
|
||||
}
|
||||
Reference in New Issue
Block a user