feat: 注入 harness 并补齐关键全局内建
This commit is contained in:
@@ -226,6 +226,7 @@ impl CompileCtx {
|
|||||||
"NaN",
|
"NaN",
|
||||||
"undefined",
|
"undefined",
|
||||||
"Infinity",
|
"Infinity",
|
||||||
|
"globalThis",
|
||||||
"Object",
|
"Object",
|
||||||
"Array",
|
"Array",
|
||||||
"String",
|
"String",
|
||||||
|
|||||||
@@ -398,6 +398,12 @@ fn compile_infinity_ok() {
|
|||||||
assert!(!module.bytecode.is_empty());
|
assert!(!module.bytecode.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_global_this_ok() {
|
||||||
|
let module = compile_source("globalThis");
|
||||||
|
assert!(!module.bytecode.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn compile_neg_infinity_ok() {
|
fn compile_neg_infinity_ok() {
|
||||||
let module = compile_source("1 / -Infinity");
|
let module = compile_source("1 / -Infinity");
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ pub struct ObjectMethods {
|
|||||||
pub keys: *const (),
|
pub keys: *const (),
|
||||||
pub create: *const (),
|
pub create: *const (),
|
||||||
pub assign: *const (),
|
pub assign: *const (),
|
||||||
|
pub is: *const (),
|
||||||
pub define_property: *const (),
|
pub define_property: *const (),
|
||||||
pub get_own_property_descriptor: *const (),
|
pub get_own_property_descriptor: *const (),
|
||||||
pub freeze: *const (),
|
pub freeze: *const (),
|
||||||
@@ -60,6 +61,7 @@ pub struct ObjectMethods {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct ArrayMethods {
|
pub struct ArrayMethods {
|
||||||
|
pub is_array: *const (),
|
||||||
pub push: *const (),
|
pub push: *const (),
|
||||||
pub pop: *const (),
|
pub pop: *const (),
|
||||||
pub slice: *const (),
|
pub slice: *const (),
|
||||||
@@ -389,6 +391,7 @@ impl BuiltinWorld {
|
|||||||
("keys", methods.keys, 1),
|
("keys", methods.keys, 1),
|
||||||
("create", methods.create, 2),
|
("create", methods.create, 2),
|
||||||
("assign", methods.assign, 2),
|
("assign", methods.assign, 2),
|
||||||
|
("is", methods.is, 2),
|
||||||
("defineProperty", methods.define_property, 3),
|
("defineProperty", methods.define_property, 3),
|
||||||
("getOwnPropertyDescriptor", methods.get_own_property_descriptor, 2),
|
("getOwnPropertyDescriptor", methods.get_own_property_descriptor, 2),
|
||||||
("freeze", methods.freeze, 1),
|
("freeze", methods.freeze, 1),
|
||||||
@@ -408,6 +411,10 @@ impl BuiltinWorld {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn bind_array_methods(&self, methods: &ArrayMethods, string_forge: &StringForge, shape_forge: &ShapeForge) {
|
pub fn bind_array_methods(&self, methods: &ArrayMethods, string_forge: &StringForge, shape_forge: &ShapeForge) {
|
||||||
|
let ctor_ptr = P::as_ptr(&self.array_constructor) as *mut JsObject;
|
||||||
|
let ctor = unsafe { &mut *ctor_ptr };
|
||||||
|
bind_methods!(self, ctor, string_forge, shape_forge, ("isArray", methods.is_array, 1),);
|
||||||
|
|
||||||
let proto_ptr = P::as_ptr(&self.array_proto) as *mut JsObject;
|
let proto_ptr = P::as_ptr(&self.array_proto) as *mut JsObject;
|
||||||
let proto = unsafe { &mut *proto_ptr };
|
let proto = unsafe { &mut *proto_ptr };
|
||||||
bind_methods!(
|
bind_methods!(
|
||||||
@@ -591,6 +598,12 @@ impl BuiltinWorld {
|
|||||||
wrapper.set_function(true);
|
wrapper.set_function(true);
|
||||||
wrapper.set_native_fn(Some(native_fn_ptr));
|
wrapper.set_native_fn(Some(native_fn_ptr));
|
||||||
wrapper.set_native_arg_count(arg_count);
|
wrapper.set_native_arg_count(arg_count);
|
||||||
|
let si_name = string_forge.intern("name").0;
|
||||||
|
let name_shape = shape_forge.make_shape(wrapper.shape_id(), si_name);
|
||||||
|
wrapper.set_shape_id(name_shape);
|
||||||
|
wrapper
|
||||||
|
.ensure_hash_props()
|
||||||
|
.push(Box::new(JsValue::string(string_forge.intern(method_name).0, 0)));
|
||||||
let wrapper_val = JsValue::from_js_object(Box::into_raw(wrapper));
|
let wrapper_val = JsValue::from_js_object(Box::into_raw(wrapper));
|
||||||
let new_shape = shape_forge.make_shape(proto.shape_id(), si);
|
let new_shape = shape_forge.make_shape(proto.shape_id(), si);
|
||||||
proto.set_shape_id(new_shape);
|
proto.set_shape_id(new_shape);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#![allow(clippy::arc_with_non_send_sync)]
|
#![allow(clippy::arc_with_non_send_sync)]
|
||||||
|
|
||||||
use oxide_compiler::compiler::Compiler;
|
use oxide_compiler::compiler::Compiler;
|
||||||
use oxide_compiler::module::CompiledModule;
|
|
||||||
use oxide_kernel::kernel::{KernelConfig, OxideKernel};
|
use oxide_kernel::kernel::{KernelConfig, OxideKernel};
|
||||||
use oxide_vm::vm::{init_kernel_builtins, Vm};
|
use oxide_vm::vm::{init_kernel_builtins, Vm};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@@ -115,6 +114,115 @@ struct RunConfig {
|
|||||||
no_skip: bool,
|
no_skip: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct HarnessSources {
|
||||||
|
sources: HashMap<&'static str, &'static str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HarnessSources {
|
||||||
|
fn new() -> Self {
|
||||||
|
let mut sources = HashMap::new();
|
||||||
|
sources.insert("sta.js", include_str!("../../../tests/test262/harness/sta.js"));
|
||||||
|
sources.insert("assert.js", include_str!("../../../tests/test262/harness/assert.js"));
|
||||||
|
sources.insert("propertyHelper.js", include_str!("../../../tests/test262/harness/propertyHelper.js"));
|
||||||
|
sources.insert("compareArray.js", include_str!("../../../tests/test262/harness/compareArray.js"));
|
||||||
|
sources.insert("fnGlobalObject.js", include_str!("../../../tests/test262/harness/fnGlobalObject.js"));
|
||||||
|
sources.insert("nans.js", include_str!("../../../tests/test262/harness/nans.js"));
|
||||||
|
sources.insert("dateConstants.js", include_str!("../../../tests/test262/harness/dateConstants.js"));
|
||||||
|
sources.insert(
|
||||||
|
"decimalToHexString.js",
|
||||||
|
include_str!("../../../tests/test262/harness/decimalToHexString.js"),
|
||||||
|
);
|
||||||
|
sources.insert("isConstructor.js", include_str!("../../../tests/test262/harness/isConstructor.js"));
|
||||||
|
sources.insert("nativeErrors.js", include_str!("../../../tests/test262/harness/nativeErrors.js"));
|
||||||
|
sources.insert(
|
||||||
|
"nativeFunctionMatcher.js",
|
||||||
|
include_str!("../../../tests/test262/harness/nativeFunctionMatcher.js"),
|
||||||
|
);
|
||||||
|
sources.insert("regExpUtils.js", include_str!("../../../tests/test262/harness/regExpUtils.js"));
|
||||||
|
sources.insert(
|
||||||
|
"assertRelativeDateMs.js",
|
||||||
|
include_str!("../../../tests/test262/harness/assertRelativeDateMs.js"),
|
||||||
|
);
|
||||||
|
sources.insert(
|
||||||
|
"wellKnownIntrinsicObjects.js",
|
||||||
|
include_str!("../../../tests/test262/harness/wellKnownIntrinsicObjects.js"),
|
||||||
|
);
|
||||||
|
sources.insert("typeCoercion.js", include_str!("../../../tests/test262/harness/typeCoercion.js"));
|
||||||
|
sources.insert("deepEqual.js", include_str!("../../../tests/test262/harness/deepEqual.js"));
|
||||||
|
Self { sources }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, name: &str) -> Option<&'static str> {
|
||||||
|
self.sources.get(name).copied()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_blacklisted_harness(name: &str) -> bool {
|
||||||
|
matches!(
|
||||||
|
name,
|
||||||
|
"testTypedArray.js"
|
||||||
|
| "testIntl.js"
|
||||||
|
| "testAtomics.js"
|
||||||
|
| "atomicsHelper.js"
|
||||||
|
| "proxyTrapsHelper.js"
|
||||||
|
| "temporalHelpers.js"
|
||||||
|
| "tcoHelper.js"
|
||||||
|
| "asyncHelpers.js"
|
||||||
|
| "promiseHelper.js"
|
||||||
|
| "detachArrayBuffer.js"
|
||||||
|
| "resizableArrayBufferUtils.js"
|
||||||
|
| "byteConversionValues.js"
|
||||||
|
| "compareIterator.js"
|
||||||
|
| "iteratorZipUtils.js"
|
||||||
|
| "doneprintHandle.js"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test262_error_prelude() -> &'static str {
|
||||||
|
r#"
|
||||||
|
function Test262Error(message) {
|
||||||
|
this.message = message;
|
||||||
|
this.name = "Test262Error";
|
||||||
|
}
|
||||||
|
Test262Error.prototype = new Error();
|
||||||
|
Test262Error.prototype.constructor = Test262Error;
|
||||||
|
"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_source_chunk(out: &mut String, name: &str, source: &str) {
|
||||||
|
out.push_str("\n// ---- test262 harness: ");
|
||||||
|
out.push_str(name);
|
||||||
|
out.push_str(" ----\n");
|
||||||
|
out.push_str(source);
|
||||||
|
out.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_test_source(meta: &TestMeta, source: &str, harness: &HarnessSources) -> Result<String, String> {
|
||||||
|
let mut full_source = String::new();
|
||||||
|
append_source_chunk(&mut full_source, "Test262Error prelude", test262_error_prelude());
|
||||||
|
append_source_chunk(
|
||||||
|
&mut full_source,
|
||||||
|
"sta.js",
|
||||||
|
harness.get("sta.js").ok_or_else(|| String::from("unknown harness: sta.js"))?,
|
||||||
|
);
|
||||||
|
append_source_chunk(
|
||||||
|
&mut full_source,
|
||||||
|
"assert.js",
|
||||||
|
harness
|
||||||
|
.get("assert.js")
|
||||||
|
.ok_or_else(|| String::from("unknown harness: assert.js"))?,
|
||||||
|
);
|
||||||
|
for include in &meta.includes {
|
||||||
|
if is_blacklisted_harness(include) {
|
||||||
|
return Err(format!("out-of-scope harness: {include}"));
|
||||||
|
}
|
||||||
|
let include_source = harness.get(include).ok_or_else(|| format!("unknown harness: {include}"))?;
|
||||||
|
append_source_chunk(&mut full_source, include, include_source);
|
||||||
|
}
|
||||||
|
append_source_chunk(&mut full_source, "test source", strip_meta(source));
|
||||||
|
Ok(full_source)
|
||||||
|
}
|
||||||
|
|
||||||
impl RunConfig {
|
impl RunConfig {
|
||||||
fn parse(args: &[String]) -> Result<Self, String> {
|
fn parse(args: &[String]) -> Result<Self, String> {
|
||||||
let mut config = Self::default();
|
let mut config = Self::default();
|
||||||
@@ -223,33 +331,13 @@ fn is_skipped(meta: &TestMeta) -> Option<String> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_harness(_kernel: &Arc<OxideKernel>) -> Result<CompiledModule, String> {
|
|
||||||
let prelude = r#"
|
|
||||||
function Test262Error(message) {
|
|
||||||
this.message = message;
|
|
||||||
this.name = "Test262Error";
|
|
||||||
}
|
|
||||||
Test262Error.prototype = new Error();
|
|
||||||
Test262Error.prototype.constructor = Test262Error;
|
|
||||||
"#;
|
|
||||||
let assert_js = include_str!("../../../tests/test262/harness/assert.js");
|
|
||||||
let full_source = format!("{prelude}\n{assert_js}");
|
|
||||||
|
|
||||||
let alloc = oxide_parser::Allocator::default();
|
|
||||||
let program =
|
|
||||||
oxide_parser::parse(&alloc, &full_source).map_err(|e| format!("harness parse error: {}", e[0].message))?;
|
|
||||||
|
|
||||||
Compiler::new().compile(&program)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_test(
|
fn run_test(
|
||||||
path: &Path, source: &str, meta: &TestMeta, kernel: &Arc<OxideKernel>, harness_module: &CompiledModule,
|
path: &Path, source: &str, meta: &TestMeta, kernel: &Arc<OxideKernel>, harness: &HarnessSources, no_skip: bool,
|
||||||
no_skip: bool,
|
|
||||||
) -> TestResult {
|
) -> TestResult {
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
|
|
||||||
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||||
run_test_inner(path, source, meta, kernel, harness_module, no_skip)
|
run_test_inner(path, source, meta, kernel, harness, no_skip)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
@@ -262,15 +350,23 @@ fn run_test(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_test_inner(
|
fn run_test_inner(
|
||||||
path: &Path, source: &str, meta: &TestMeta, kernel: &Arc<OxideKernel>, harness_module: &CompiledModule,
|
path: &Path, source: &str, meta: &TestMeta, kernel: &Arc<OxideKernel>, harness: &HarnessSources, no_skip: bool,
|
||||||
no_skip: bool,
|
|
||||||
) -> TestResult {
|
) -> TestResult {
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
|
|
||||||
let code = strip_meta(source);
|
let code = match build_test_source(meta, source, harness) {
|
||||||
|
Ok(code) => code,
|
||||||
|
Err(e) => {
|
||||||
|
let dur = start.elapsed().as_millis() as u64;
|
||||||
|
if no_skip {
|
||||||
|
return TestResult::fail(path.to_path_buf(), dur, e);
|
||||||
|
}
|
||||||
|
return TestResult::skip(path.to_path_buf(), e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let alloc = oxide_parser::Allocator::default();
|
let alloc = oxide_parser::Allocator::default();
|
||||||
let program = match oxide_parser::parse(&alloc, code) {
|
let program = match oxide_parser::parse(&alloc, &code) {
|
||||||
Ok(p) => p,
|
Ok(p) => p,
|
||||||
Err(errs) => {
|
Err(errs) => {
|
||||||
let dur = start.elapsed().as_millis() as u64;
|
let dur = start.elapsed().as_millis() as u64;
|
||||||
@@ -314,15 +410,6 @@ fn run_test_inner(
|
|||||||
|
|
||||||
let mut vm = Vm::with_kernel(Arc::clone(kernel));
|
let mut vm = Vm::with_kernel(Arc::clone(kernel));
|
||||||
|
|
||||||
if let Err(e) = vm.run(harness_module) {
|
|
||||||
let dur = start.elapsed().as_millis() as u64;
|
|
||||||
let msg = format!("harness injection failed: {e}");
|
|
||||||
if no_skip {
|
|
||||||
return TestResult::fail(path.to_path_buf(), dur, msg);
|
|
||||||
}
|
|
||||||
return TestResult::skip(path.to_path_buf(), msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
match vm.run(&module) {
|
match vm.run(&module) {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
let dur = start.elapsed().as_millis() as u64;
|
let dur = start.elapsed().as_millis() as u64;
|
||||||
@@ -417,6 +504,14 @@ fn categorize_fail(msg: &str) -> String {
|
|||||||
}
|
}
|
||||||
} else if msg.contains("engine panic") {
|
} else if msg.contains("engine panic") {
|
||||||
"engine panic".into()
|
"engine panic".into()
|
||||||
|
} else if msg.contains("out-of-scope harness:") {
|
||||||
|
"harness: blacklisted".into()
|
||||||
|
} else if msg.contains("unknown harness:") {
|
||||||
|
"harness: unknown".into()
|
||||||
|
} else if msg.contains("harness compile error:") {
|
||||||
|
"harness: compile error".into()
|
||||||
|
} else if msg.contains("harness runtime error:") {
|
||||||
|
"harness: runtime error".into()
|
||||||
} else if msg.contains("expected runtime error") {
|
} else if msg.contains("expected runtime error") {
|
||||||
"expected runtime error".into()
|
"expected runtime error".into()
|
||||||
} else {
|
} else {
|
||||||
@@ -471,7 +566,7 @@ fn run_tests() -> bool {
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let filter = config.filter.clone();
|
let filter = config.filter.clone().map(|f| f.replace('\\', "/"));
|
||||||
|
|
||||||
eprintln!("discovering tests in: {}", test262_root.display());
|
eprintln!("discovering tests in: {}", test262_root.display());
|
||||||
if config.no_skip {
|
if config.no_skip {
|
||||||
@@ -482,14 +577,7 @@ fn run_tests() -> bool {
|
|||||||
|
|
||||||
let kernel = Arc::new(OxideKernel::new(KernelConfig::minimal()));
|
let kernel = Arc::new(OxideKernel::new(KernelConfig::minimal()));
|
||||||
init_kernel_builtins(&kernel);
|
init_kernel_builtins(&kernel);
|
||||||
|
let harness_sources = HarnessSources::new();
|
||||||
let harness_module = match compile_harness(&kernel) {
|
|
||||||
Ok(m) => m,
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("FATAL: Failed to compile test262 harness: {e}");
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut stats = RunStats::default();
|
let mut stats = RunStats::default();
|
||||||
let mut results: Vec<TestResult> = Vec::new();
|
let mut results: Vec<TestResult> = Vec::new();
|
||||||
@@ -527,7 +615,7 @@ fn run_tests() -> bool {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(filter_str) = &filter {
|
if let Some(filter_str) = &filter {
|
||||||
let path_str = path.to_string_lossy();
|
let path_str = path.to_string_lossy().replace('\\', "/");
|
||||||
if !path_str.contains(filter_str.as_str()) {
|
if !path_str.contains(filter_str.as_str()) {
|
||||||
results.push(TestResult::skip(path.clone(), "filtered".into()));
|
results.push(TestResult::skip(path.clone(), "filtered".into()));
|
||||||
stats.skip += 1;
|
stats.skip += 1;
|
||||||
@@ -550,7 +638,7 @@ fn run_tests() -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = run_test(path, &source, &meta, &kernel, &harness_module, config.no_skip);
|
let result = run_test(path, &source, &meta, &kernel, &harness_sources, config.no_skip);
|
||||||
match &result.outcome {
|
match &result.outcome {
|
||||||
TestOutcome::Pass(_) => stats.pass += 1,
|
TestOutcome::Pass(_) => stats.pass += 1,
|
||||||
TestOutcome::Fail(msg) => {
|
TestOutcome::Fail(msg) => {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use oxide_types::object::JsObject;
|
|||||||
|
|
||||||
pub fn bind_array(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
|
pub fn bind_array(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
|
||||||
let _array_methods = ArrayMethods {
|
let _array_methods = ArrayMethods {
|
||||||
|
is_array: crate::builtins::array::array_is_array as *const (),
|
||||||
push: crate::builtins::array::array_push as *const (),
|
push: crate::builtins::array::array_push as *const (),
|
||||||
pop: crate::builtins::array::array_pop as *const (),
|
pop: crate::builtins::array::array_pop as *const (),
|
||||||
slice: crate::builtins::array::array_slice as *const (),
|
slice: crate::builtins::array::array_slice as *const (),
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ pub fn bind_object(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
|
|||||||
keys: crate::builtins::object::object_keys as *const (),
|
keys: crate::builtins::object::object_keys as *const (),
|
||||||
create: crate::builtins::object::object_create as *const (),
|
create: crate::builtins::object::object_create as *const (),
|
||||||
assign: crate::builtins::object::object_assign as *const (),
|
assign: crate::builtins::object::object_assign as *const (),
|
||||||
|
is: crate::builtins::object::object_is as *const (),
|
||||||
define_property: crate::builtins::object::object_define_property as *const (),
|
define_property: crate::builtins::object::object_define_property as *const (),
|
||||||
get_own_property_descriptor: crate::builtins::object::object_get_own_property_descriptor as *const (),
|
get_own_property_descriptor: crate::builtins::object::object_get_own_property_descriptor as *const (),
|
||||||
freeze: crate::builtins::object::object_freeze as *const (),
|
freeze: crate::builtins::object::object_freeze as *const (),
|
||||||
|
|||||||
@@ -90,4 +90,6 @@ pub fn init_kernel_builtins(kernel: &Arc<OxideKernel>) {
|
|||||||
bind_function::bind_function(kernel);
|
bind_function::bind_function(kernel);
|
||||||
bind_regexp::bind_regexp(kernel, global);
|
bind_regexp::bind_regexp(kernel, global);
|
||||||
bind_symbol::bind_symbol(kernel, global);
|
bind_symbol::bind_symbol(kernel, global);
|
||||||
|
|
||||||
|
bind_global_value(kernel, global, "globalThis", JsValue::from_js_object(global_ptr));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,21 @@ pub fn array_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
|||||||
Ok(JsValue::from_js_object(arr))
|
Ok(JsValue::from_js_object(arr))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn array_is_array(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
|
if args.len() < 2 {
|
||||||
|
return Ok(JsValue::bool(false));
|
||||||
|
}
|
||||||
|
let val = vm.reg(args[1]);
|
||||||
|
if !val.is_object() {
|
||||||
|
return Ok(JsValue::bool(false));
|
||||||
|
}
|
||||||
|
let ptr = val.as_js_object_ptr();
|
||||||
|
if ptr.is_null() {
|
||||||
|
return Ok(JsValue::bool(false));
|
||||||
|
}
|
||||||
|
Ok(JsValue::bool(unsafe { &*ptr }.is_array()))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn array_push(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
pub fn array_push(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
let arr_ptr = array_ptr!(vm, args);
|
let arr_ptr = array_ptr!(vm, args);
|
||||||
let arr = unsafe { &mut *arr_ptr };
|
let arr = unsafe { &mut *arr_ptr };
|
||||||
|
|||||||
@@ -131,6 +131,12 @@ pub fn object_assign(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
|||||||
Ok(target_val)
|
Ok(target_val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn object_is(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
|
let lhs = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||||
|
let rhs = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||||
|
Ok(JsValue::bool(coercion::same_value(lhs, rhs)))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn object_define_property(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
pub fn object_define_property(vm: &mut Vm, args: &[u8]) -> NativeResult {
|
||||||
if args.len() < 4 {
|
if args.len() < 4 {
|
||||||
return Err(JsValue::undefined());
|
return Err(JsValue::undefined());
|
||||||
|
|||||||
@@ -187,6 +187,15 @@ fn array_empty_literal() {
|
|||||||
assert_eq!(obj.prop_count(), 0);
|
assert_eq!(obj.prop_count(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn array_is_array_static_method() {
|
||||||
|
let (_vm, result) = eval("Array.isArray([])").unwrap();
|
||||||
|
assert_eq!(result, JsValue::bool(true));
|
||||||
|
|
||||||
|
let (_vm, result) = eval("Array.isArray({})").unwrap();
|
||||||
|
assert_eq!(result, JsValue::bool(false));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn array_shift_removes_first() {
|
fn array_shift_removes_first() {
|
||||||
let (_vm, result) = eval("[1,2,3].shift()").unwrap();
|
let (_vm, result) = eval("[1,2,3].shift()").unwrap();
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ fn function_to_string_includes_function() {
|
|||||||
let result = eval(&mut vm, "Math.max.toString()").unwrap();
|
let result = eval(&mut vm, "Math.max.toString()").unwrap();
|
||||||
assert!(result.is_string());
|
assert!(result.is_string());
|
||||||
let rendered = vm.kernel().string_forge().lookup(result.as_string_index()).unwrap_or_default();
|
let rendered = vm.kernel().string_forge().lookup(result.as_string_index()).unwrap_or_default();
|
||||||
assert!(rendered.contains("function"), "got: {}", rendered);
|
assert_eq!(rendered, "function max() { [native code] }");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -76,3 +76,10 @@ fn division_by_zero_gives_infinity() {
|
|||||||
assert!(result.is_double());
|
assert!(result.is_double());
|
||||||
assert!(result.as_double().is_infinite());
|
assert!(result.as_double().is_infinite());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn global_this_points_to_global_object() {
|
||||||
|
let result = eval("globalThis.Object === Object").unwrap();
|
||||||
|
assert!(result.is_bool());
|
||||||
|
assert!(result.as_bool());
|
||||||
|
}
|
||||||
|
|||||||
@@ -37,6 +37,18 @@ fn object_assign_copies() {
|
|||||||
assert!(result.is_object());
|
assert!(result.is_object());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn object_is_uses_same_value_semantics() {
|
||||||
|
let (_vm, result) = eval("Object.is(NaN, NaN)").unwrap();
|
||||||
|
assert_eq!(result, JsValue::bool(true));
|
||||||
|
|
||||||
|
let (_vm, result) = eval("Object.is(1, 1)").unwrap();
|
||||||
|
assert_eq!(result, JsValue::bool(true));
|
||||||
|
|
||||||
|
let (_vm, result) = eval("Object.is(1, '1')").unwrap();
|
||||||
|
assert_eq!(result, JsValue::bool(false));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn object_get_own_property_descriptor_value() {
|
fn object_get_own_property_descriptor_value() {
|
||||||
let (_vm, result) = eval("Object.getOwnPropertyDescriptor({a:1},'a')").unwrap();
|
let (_vm, result) = eval("Object.getOwnPropertyDescriptor({a:1},'a')").unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user