// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use crate::ascii_str; use crate::error::custom_error; use crate::error::generic_error; use crate::error::AnyError; use crate::error::JsError; use crate::extensions::OpDecl; use crate::include_ascii_string; use crate::module_specifier::ModuleSpecifier; use crate::modules::AssertedModuleType; use crate::modules::ModuleCode; use crate::modules::ModuleInfo; use crate::modules::ModuleLoadId; use crate::modules::ModuleLoader; use crate::modules::ModuleSource; use crate::modules::ModuleSourceFuture; use crate::modules::ModuleType; use crate::modules::ResolutionKind; use crate::modules::SymbolicModule; use crate::Extension; use crate::JsBuffer; use crate::*; use anyhow::Error; use deno_ops::op; use futures::future::poll_fn; use futures::future::Future; use futures::FutureExt; use std::cell::RefCell; use std::pin::Pin; use std::rc::Rc; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; use std::sync::Arc; use std::task::Context; use std::task::Poll; // deno_ops macros generate code assuming deno_core in scope. mod deno_core { pub use crate::*; } #[derive(Copy, Clone)] pub enum Mode { Async, AsyncDeferred, AsyncZeroCopy(bool), } struct TestState { mode: Mode, dispatch_count: Arc, } #[op] async fn op_test( rc_op_state: Rc>, control: u8, buf: Option, ) -> Result { #![allow(clippy::await_holding_refcell_ref)] // False positive. let op_state_ = rc_op_state.borrow(); let test_state = op_state_.borrow::(); test_state.dispatch_count.fetch_add(1, Ordering::Relaxed); let mode = test_state.mode; drop(op_state_); match mode { Mode::Async => { assert_eq!(control, 42); Ok(43) } Mode::AsyncDeferred => { tokio::task::yield_now().await; assert_eq!(control, 42); Ok(43) } Mode::AsyncZeroCopy(has_buffer) => { assert_eq!(buf.is_some(), has_buffer); if let Some(buf) = buf { assert_eq!(buf.len(), 1); } Ok(43) } } } fn setup(mode: Mode) -> (JsRuntime, Arc) { let dispatch_count = Arc::new(AtomicUsize::new(0)); deno_core::extension!( test_ext, ops = [op_test], options = { mode: Mode, dispatch_count: Arc, }, state = |state, options| { state.put(TestState { mode: options.mode, dispatch_count: options.dispatch_count }) } ); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![test_ext::init_ops(mode, dispatch_count.clone())], get_error_class_fn: Some(&|error| { crate::error::get_custom_error_class(error).unwrap() }), ..Default::default() }); runtime .execute_script_static( "setup.js", r#" function assert(cond) { if (!cond) { throw Error("assert"); } } "#, ) .unwrap(); assert_eq!(dispatch_count.load(Ordering::Relaxed), 0); (runtime, dispatch_count) } #[tokio::test] async fn test_ref_unref_ops() { let (mut runtime, _dispatch_count) = setup(Mode::AsyncDeferred); runtime .execute_script_static( "filename.js", r#" var promiseIdSymbol = Symbol.for("Deno.core.internalPromiseId"); var p1 = Deno.core.opAsync("op_test", 42); var p2 = Deno.core.opAsync("op_test", 42); "#, ) .unwrap(); { let realm = runtime.global_realm(); assert_eq!(realm.num_pending_ops(), 2); assert_eq!(realm.num_unrefed_ops(), 0); } runtime .execute_script_static( "filename.js", r#" Deno.core.ops.op_unref_op(p1[promiseIdSymbol]); Deno.core.ops.op_unref_op(p2[promiseIdSymbol]); "#, ) .unwrap(); { let realm = runtime.global_realm(); assert_eq!(realm.num_pending_ops(), 2); assert_eq!(realm.num_unrefed_ops(), 2); } runtime .execute_script_static( "filename.js", r#" Deno.core.ops.op_ref_op(p1[promiseIdSymbol]); Deno.core.ops.op_ref_op(p2[promiseIdSymbol]); "#, ) .unwrap(); { let realm = runtime.global_realm(); assert_eq!(realm.num_pending_ops(), 2); assert_eq!(realm.num_unrefed_ops(), 0); } } #[test] fn test_dispatch() { let (mut runtime, dispatch_count) = setup(Mode::Async); runtime .execute_script_static( "filename.js", r#" let control = 42; Deno.core.opAsync("op_test", control); async function main() { Deno.core.opAsync("op_test", control); } main(); "#, ) .unwrap(); assert_eq!(dispatch_count.load(Ordering::Relaxed), 2); } #[test] fn test_op_async_promise_id() { let (mut runtime, _dispatch_count) = setup(Mode::Async); runtime .execute_script_static( "filename.js", r#" const p = Deno.core.opAsync("op_test", 42); if (p[Symbol.for("Deno.core.internalPromiseId")] == undefined) { throw new Error("missing id on returned promise"); } "#, ) .unwrap(); } #[test] fn test_dispatch_no_zero_copy_buf() { let (mut runtime, dispatch_count) = setup(Mode::AsyncZeroCopy(false)); runtime .execute_script_static( "filename.js", r#" Deno.core.opAsync("op_test", 0); "#, ) .unwrap(); assert_eq!(dispatch_count.load(Ordering::Relaxed), 1); } #[test] fn test_dispatch_stack_zero_copy_bufs() { let (mut runtime, dispatch_count) = setup(Mode::AsyncZeroCopy(true)); runtime .execute_script_static( "filename.js", r#" const { op_test } = Deno.core.ensureFastOps(); let zero_copy_a = new Uint8Array([0]); op_test(0, zero_copy_a); "#, ) .unwrap(); assert_eq!(dispatch_count.load(Ordering::Relaxed), 1); } #[test] fn test_execute_script_return_value() { let mut runtime = JsRuntime::new(Default::default()); let value_global = runtime.execute_script_static("a.js", "a = 1 + 2").unwrap(); { let scope = &mut runtime.handle_scope(); let value = value_global.open(scope); assert_eq!(value.integer_value(scope).unwrap(), 3); } let value_global = runtime .execute_script_static("b.js", "b = 'foobar'") .unwrap(); { let scope = &mut runtime.handle_scope(); let value = value_global.open(scope); assert!(value.is_string()); assert_eq!( value.to_string(scope).unwrap().to_rust_string_lossy(scope), "foobar" ); } } #[tokio::test] async fn test_poll_value() { let mut runtime = JsRuntime::new(Default::default()); poll_fn(move |cx| { let value_global = runtime .execute_script_static("a.js", "Promise.resolve(1 + 2)") .unwrap(); let v = runtime.poll_value(&value_global, cx); { let scope = &mut runtime.handle_scope(); assert!( matches!(v, Poll::Ready(Ok(v)) if v.open(scope).integer_value(scope).unwrap() == 3) ); } let value_global = runtime .execute_script_static( "a.js", "Promise.resolve(new Promise(resolve => resolve(2 + 2)))", ) .unwrap(); let v = runtime.poll_value(&value_global, cx); { let scope = &mut runtime.handle_scope(); assert!( matches!(v, Poll::Ready(Ok(v)) if v.open(scope).integer_value(scope).unwrap() == 4) ); } let value_global = runtime .execute_script_static("a.js", "Promise.reject(new Error('fail'))") .unwrap(); let v = runtime.poll_value(&value_global, cx); assert!( matches!(v, Poll::Ready(Err(e)) if e.downcast_ref::().unwrap().exception_message == "Uncaught Error: fail") ); let value_global = runtime .execute_script_static("a.js", "new Promise(resolve => {})") .unwrap(); let v = runtime.poll_value(&value_global, cx); matches!(v, Poll::Ready(Err(e)) if e.to_string() == "Promise resolution is still pending but the event loop has already resolved."); Poll::Ready(()) }).await; } #[tokio::test] async fn test_resolve_value() { let mut runtime = JsRuntime::new(Default::default()); let value_global = runtime .execute_script_static("a.js", "Promise.resolve(1 + 2)") .unwrap(); let result_global = runtime.resolve_value(value_global).await.unwrap(); { let scope = &mut runtime.handle_scope(); let value = result_global.open(scope); assert_eq!(value.integer_value(scope).unwrap(), 3); } let value_global = runtime .execute_script_static( "a.js", "Promise.resolve(new Promise(resolve => resolve(2 + 2)))", ) .unwrap(); let result_global = runtime.resolve_value(value_global).await.unwrap(); { let scope = &mut runtime.handle_scope(); let value = result_global.open(scope); assert_eq!(value.integer_value(scope).unwrap(), 4); } let value_global = runtime .execute_script_static("a.js", "Promise.reject(new Error('fail'))") .unwrap(); let err = runtime.resolve_value(value_global).await.unwrap_err(); assert_eq!( "Uncaught Error: fail", err.downcast::().unwrap().exception_message ); let value_global = runtime .execute_script_static("a.js", "new Promise(resolve => {})") .unwrap(); let error_string = runtime .resolve_value(value_global) .await .unwrap_err() .to_string(); assert_eq!( "Promise resolution is still pending but the event loop has already resolved.", error_string, ); } #[test] fn terminate_execution_webassembly() { let (mut runtime, _dispatch_count) = setup(Mode::Async); let v8_isolate_handle = runtime.v8_isolate().thread_safe_handle(); // Run an infinite loop in Webassemby code, which should be terminated. let promise = runtime.execute_script_static("infinite_wasm_loop.js", r#" (async () => { const wasmCode = new Uint8Array([ 0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 3, 2, 1, 0, 7, 17, 1, 13, 105, 110, 102, 105, 110, 105, 116, 101, 95, 108, 111, 111, 112, 0, 0, 10, 9, 1, 7, 0, 3, 64, 12, 0, 11, 11, ]); const wasmModule = await WebAssembly.compile(wasmCode); globalThis.wasmInstance = new WebAssembly.Instance(wasmModule); })() "#).unwrap(); futures::executor::block_on(runtime.resolve_value(promise)).unwrap(); let terminator_thread = std::thread::spawn(move || { std::thread::sleep(std::time::Duration::from_millis(1000)); // terminate execution let ok = v8_isolate_handle.terminate_execution(); assert!(ok); }); let err = runtime .execute_script_static( "infinite_wasm_loop2.js", "globalThis.wasmInstance.exports.infinite_loop();", ) .unwrap_err(); assert_eq!(err.to_string(), "Uncaught Error: execution terminated"); // Cancel the execution-terminating exception in order to allow script // execution again. let ok = runtime.v8_isolate().cancel_terminate_execution(); assert!(ok); // Verify that the isolate usable again. runtime .execute_script_static("simple.js", "1 + 1") .expect("execution should be possible again"); terminator_thread.join().unwrap(); } #[test] fn terminate_execution() { let (mut isolate, _dispatch_count) = setup(Mode::Async); let v8_isolate_handle = isolate.v8_isolate().thread_safe_handle(); let terminator_thread = std::thread::spawn(move || { // allow deno to boot and run std::thread::sleep(std::time::Duration::from_millis(100)); // terminate execution let ok = v8_isolate_handle.terminate_execution(); assert!(ok); }); // Rn an infinite loop, which should be terminated. match isolate.execute_script_static("infinite_loop.js", "for(;;) {}") { Ok(_) => panic!("execution should be terminated"), Err(e) => { assert_eq!(e.to_string(), "Uncaught Error: execution terminated") } }; // Cancel the execution-terminating exception in order to allow script // execution again. let ok = isolate.v8_isolate().cancel_terminate_execution(); assert!(ok); // Verify that the isolate usable again. isolate .execute_script_static("simple.js", "1 + 1") .expect("execution should be possible again"); terminator_thread.join().unwrap(); } #[test] fn dangling_shared_isolate() { let v8_isolate_handle = { // isolate is dropped at the end of this block let (mut runtime, _dispatch_count) = setup(Mode::Async); runtime.v8_isolate().thread_safe_handle() }; // this should not SEGFAULT v8_isolate_handle.terminate_execution(); } #[test] fn syntax_error() { let mut runtime = JsRuntime::new(Default::default()); let src = "hocuspocus("; let r = runtime.execute_script_static("i.js", src); let e = r.unwrap_err(); let js_error = e.downcast::().unwrap(); let frame = js_error.frames.first().unwrap(); assert_eq!(frame.column_number, Some(12)); } #[tokio::test] async fn test_encode_decode() { let (mut runtime, _dispatch_count) = setup(Mode::Async); poll_fn(move |cx| { runtime .execute_script( "encode_decode_test.js", // Note: We make this to_owned because it contains non-ASCII chars include_str!("encode_decode_test.js").to_owned().into(), ) .unwrap(); if let Poll::Ready(Err(_)) = runtime.poll_event_loop(cx, false) { unreachable!(); } Poll::Ready(()) }) .await; } #[tokio::test] async fn test_serialize_deserialize() { let (mut runtime, _dispatch_count) = setup(Mode::Async); poll_fn(move |cx| { runtime .execute_script( "serialize_deserialize_test.js", include_ascii_string!("serialize_deserialize_test.js"), ) .unwrap(); if let Poll::Ready(Err(_)) = runtime.poll_event_loop(cx, false) { unreachable!(); } Poll::Ready(()) }) .await; } #[tokio::test] async fn test_error_builder() { #[op] fn op_err() -> Result<(), Error> { Err(custom_error("DOMExceptionOperationError", "abc")) } pub fn get_error_class_name(_: &Error) -> &'static str { "DOMExceptionOperationError" } deno_core::extension!(test_ext, ops = [op_err]); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![test_ext::init_ops()], get_error_class_fn: Some(&get_error_class_name), ..Default::default() }); poll_fn(move |cx| { runtime .execute_script_static( "error_builder_test.js", include_str!("error_builder_test.js"), ) .unwrap(); if let Poll::Ready(Err(_)) = runtime.poll_event_loop(cx, false) { unreachable!(); } Poll::Ready(()) }) .await; } /// Ensure that putting the inspector into OpState doesn't cause crashes. The only valid place we currently allow /// the inspector to be stashed without cleanup is the OpState, and this should not actually cause crashes. #[test] fn inspector() { let mut runtime = JsRuntime::new(RuntimeOptions { inspector: true, ..Default::default() }); // This was causing a crash runtime.op_state().borrow_mut().put(runtime.inspector()); runtime.execute_script_static("check.js", "null").unwrap(); } #[test] fn will_snapshot() { let snapshot = { let mut runtime = JsRuntimeForSnapshot::new(Default::default(), Default::default()); runtime.execute_script_static("a.js", "a = 1 + 2").unwrap(); runtime.snapshot() }; let snapshot = Snapshot::JustCreated(snapshot); let mut runtime2 = JsRuntime::new(RuntimeOptions { startup_snapshot: Some(snapshot), ..Default::default() }); runtime2 .execute_script_static("check.js", "if (a != 3) throw Error('x')") .unwrap(); } #[test] fn will_snapshot2() { let startup_data = { let mut runtime = JsRuntimeForSnapshot::new(Default::default(), Default::default()); runtime .execute_script_static("a.js", "let a = 1 + 2") .unwrap(); runtime.snapshot() }; let snapshot = Snapshot::JustCreated(startup_data); let mut runtime = JsRuntimeForSnapshot::new( RuntimeOptions { startup_snapshot: Some(snapshot), ..Default::default() }, Default::default(), ); let startup_data = { runtime .execute_script_static("check_a.js", "if (a != 3) throw Error('x')") .unwrap(); runtime.execute_script_static("b.js", "b = 2 + 3").unwrap(); runtime.snapshot() }; let snapshot = Snapshot::JustCreated(startup_data); { let mut runtime = JsRuntime::new(RuntimeOptions { startup_snapshot: Some(snapshot), ..Default::default() }); runtime .execute_script_static("check_b.js", "if (b != 5) throw Error('x')") .unwrap(); runtime .execute_script_static("check2.js", "if (!Deno.core) throw Error('x')") .unwrap(); } } #[test] fn test_snapshot_callbacks() { let snapshot = { let mut runtime = JsRuntimeForSnapshot::new(Default::default(), Default::default()); runtime .execute_script_static( "a.js", r#" Deno.core.setMacrotaskCallback(() => { return true; }); Deno.core.ops.op_set_format_exception_callback(()=> { return null; }) Deno.core.setPromiseRejectCallback(() => { return false; }); a = 1 + 2; "#, ) .unwrap(); runtime.snapshot() }; let snapshot = Snapshot::JustCreated(snapshot); let mut runtime2 = JsRuntime::new(RuntimeOptions { startup_snapshot: Some(snapshot), ..Default::default() }); runtime2 .execute_script_static("check.js", "if (a != 3) throw Error('x')") .unwrap(); } #[test] fn test_from_boxed_snapshot() { let snapshot = { let mut runtime = JsRuntimeForSnapshot::new(Default::default(), Default::default()); runtime.execute_script_static("a.js", "a = 1 + 2").unwrap(); let snap: &[u8] = &runtime.snapshot(); Vec::from(snap).into_boxed_slice() }; let snapshot = Snapshot::Boxed(snapshot); let mut runtime2 = JsRuntime::new(RuntimeOptions { startup_snapshot: Some(snapshot), ..Default::default() }); runtime2 .execute_script_static("check.js", "if (a != 3) throw Error('x')") .unwrap(); } #[test] fn test_get_module_namespace() { #[derive(Default)] struct ModsLoader; impl ModuleLoader for ModsLoader { fn resolve( &self, specifier: &str, referrer: &str, _kind: ResolutionKind, ) -> Result { assert_eq!(specifier, "file:///main.js"); assert_eq!(referrer, "."); let s = crate::resolve_import(specifier, referrer).unwrap(); Ok(s) } fn load( &self, _module_specifier: &ModuleSpecifier, _maybe_referrer: Option<&ModuleSpecifier>, _is_dyn_import: bool, ) -> Pin> { async { Err(generic_error("Module loading is not supported")) } .boxed_local() } } let loader = std::rc::Rc::new(ModsLoader::default()); let mut runtime = JsRuntime::new(RuntimeOptions { module_loader: Some(loader), ..Default::default() }); let specifier = crate::resolve_url("file:///main.js").unwrap(); let source_code = ascii_str!( r#" export const a = "b"; export default 1 + 2; "# ); let module_id = futures::executor::block_on( runtime.load_main_module(&specifier, Some(source_code)), ) .unwrap(); #[allow(clippy::let_underscore_future)] let _ = runtime.mod_evaluate(module_id); let module_namespace = runtime.get_module_namespace(module_id).unwrap(); let scope = &mut runtime.handle_scope(); let module_namespace = v8::Local::::new(scope, module_namespace); assert!(module_namespace.is_module_namespace_object()); let unknown_export_name = v8::String::new(scope, "none").unwrap(); let binding = module_namespace.get(scope, unknown_export_name.into()); assert!(binding.is_some()); assert!(binding.unwrap().is_undefined()); let empty_export_name = v8::String::new(scope, "").unwrap(); let binding = module_namespace.get(scope, empty_export_name.into()); assert!(binding.is_some()); assert!(binding.unwrap().is_undefined()); let a_export_name = v8::String::new(scope, "a").unwrap(); let binding = module_namespace.get(scope, a_export_name.into()); assert!(binding.unwrap().is_string()); assert_eq!(binding.unwrap(), v8::String::new(scope, "b").unwrap()); let default_export_name = v8::String::new(scope, "default").unwrap(); let binding = module_namespace.get(scope, default_export_name.into()); assert!(binding.unwrap().is_number()); assert_eq!(binding.unwrap(), v8::Number::new(scope, 3_f64)); } #[test] fn test_heap_limits() { let create_params = v8::Isolate::create_params().heap_limits(0, 5 * 1024 * 1024); let mut runtime = JsRuntime::new(RuntimeOptions { create_params: Some(create_params), ..Default::default() }); let cb_handle = runtime.v8_isolate().thread_safe_handle(); let callback_invoke_count = Rc::new(AtomicUsize::new(0)); let inner_invoke_count = Rc::clone(&callback_invoke_count); runtime.add_near_heap_limit_callback(move |current_limit, _initial_limit| { inner_invoke_count.fetch_add(1, Ordering::SeqCst); cb_handle.terminate_execution(); current_limit * 2 }); let err = runtime .execute_script_static( "script name", r#"let s = ""; while(true) { s += "Hello"; }"#, ) .expect_err("script should fail"); assert_eq!( "Uncaught Error: execution terminated", err.downcast::().unwrap().exception_message ); assert!(callback_invoke_count.load(Ordering::SeqCst) > 0) } #[test] fn test_heap_limit_cb_remove() { let mut runtime = JsRuntime::new(Default::default()); runtime.add_near_heap_limit_callback(|current_limit, _initial_limit| { current_limit * 2 }); runtime.remove_near_heap_limit_callback(3 * 1024 * 1024); assert!(runtime.allocations.near_heap_limit_callback_data.is_none()); } #[test] fn test_heap_limit_cb_multiple() { let create_params = v8::Isolate::create_params().heap_limits(0, 5 * 1024 * 1024); let mut runtime = JsRuntime::new(RuntimeOptions { create_params: Some(create_params), ..Default::default() }); let cb_handle = runtime.v8_isolate().thread_safe_handle(); let callback_invoke_count_first = Rc::new(AtomicUsize::new(0)); let inner_invoke_count_first = Rc::clone(&callback_invoke_count_first); runtime.add_near_heap_limit_callback(move |current_limit, _initial_limit| { inner_invoke_count_first.fetch_add(1, Ordering::SeqCst); current_limit * 2 }); let callback_invoke_count_second = Rc::new(AtomicUsize::new(0)); let inner_invoke_count_second = Rc::clone(&callback_invoke_count_second); runtime.add_near_heap_limit_callback(move |current_limit, _initial_limit| { inner_invoke_count_second.fetch_add(1, Ordering::SeqCst); cb_handle.terminate_execution(); current_limit * 2 }); let err = runtime .execute_script_static( "script name", r#"let s = ""; while(true) { s += "Hello"; }"#, ) .expect_err("script should fail"); assert_eq!( "Uncaught Error: execution terminated", err.downcast::().unwrap().exception_message ); assert_eq!(0, callback_invoke_count_first.load(Ordering::SeqCst)); assert!(callback_invoke_count_second.load(Ordering::SeqCst) > 0); } #[test] fn es_snapshot() { #[derive(Default)] struct ModsLoader; impl ModuleLoader for ModsLoader { fn resolve( &self, specifier: &str, referrer: &str, _kind: ResolutionKind, ) -> Result { let s = crate::resolve_import(specifier, referrer).unwrap(); Ok(s) } fn load( &self, _module_specifier: &ModuleSpecifier, _maybe_referrer: Option<&ModuleSpecifier>, _is_dyn_import: bool, ) -> Pin> { eprintln!("load() should not be called"); unreachable!() } } fn create_module( runtime: &mut JsRuntime, i: usize, main: bool, ) -> ModuleInfo { let specifier = crate::resolve_url(&format!("file:///{i}.js")).unwrap(); let prev = i - 1; let source_code = format!( r#" import {{ f{prev} }} from "file:///{prev}.js"; export function f{i}() {{ return f{prev}() }} "# ) .into(); let id = if main { futures::executor::block_on( runtime.load_main_module(&specifier, Some(source_code)), ) .unwrap() } else { futures::executor::block_on( runtime.load_side_module(&specifier, Some(source_code)), ) .unwrap() }; assert_eq!(i, id); #[allow(clippy::let_underscore_future)] let _ = runtime.mod_evaluate(id); futures::executor::block_on(runtime.run_event_loop(false)).unwrap(); ModuleInfo { id, main, name: specifier.into(), requests: vec![crate::modules::ModuleRequest { specifier: format!("file:///{prev}.js"), asserted_module_type: AssertedModuleType::JavaScriptOrWasm, }], module_type: ModuleType::JavaScript, } } fn assert_module_map(runtime: &mut JsRuntime, modules: &Vec) { let module_map = runtime.module_map.borrow(); assert_eq!(module_map.handles.len(), modules.len()); assert_eq!(module_map.info.len(), modules.len()); assert_eq!( module_map.by_name(AssertedModuleType::Json).len() + module_map .by_name(AssertedModuleType::JavaScriptOrWasm) .len(), modules.len() ); assert_eq!(module_map.next_load_id, (modules.len() + 1) as ModuleLoadId); for info in modules { assert!(module_map.handles.get(info.id).is_some()); assert_eq!(module_map.info.get(info.id).unwrap(), info); assert_eq!( module_map .by_name(AssertedModuleType::JavaScriptOrWasm) .get(&info.name) .unwrap(), &SymbolicModule::Mod(info.id) ); } } #[op] fn op_test() -> Result { Ok(String::from("test")) } let loader = Rc::new(ModsLoader::default()); let mut runtime = JsRuntimeForSnapshot::new( RuntimeOptions { module_loader: Some(loader.clone()), extensions: vec![Extension::builder("text_ext") .ops(vec![op_test::decl()]) .build()], ..Default::default() }, Default::default(), ); let specifier = crate::resolve_url("file:///0.js").unwrap(); let source_code = ascii_str!(r#"export function f0() { return "hello world" }"#); let id = futures::executor::block_on( runtime.load_side_module(&specifier, Some(source_code)), ) .unwrap(); #[allow(clippy::let_underscore_future)] let _ = runtime.mod_evaluate(id); futures::executor::block_on(runtime.run_event_loop(false)).unwrap(); let mut modules = vec![]; modules.push(ModuleInfo { id, main: false, name: specifier.into(), requests: vec![], module_type: ModuleType::JavaScript, }); modules.extend((1..200).map(|i| create_module(&mut runtime, i, false))); assert_module_map(&mut runtime, &modules); let snapshot = runtime.snapshot(); let mut runtime2 = JsRuntimeForSnapshot::new( RuntimeOptions { module_loader: Some(loader.clone()), startup_snapshot: Some(Snapshot::JustCreated(snapshot)), extensions: vec![Extension::builder("text_ext") .ops(vec![op_test::decl()]) .build()], ..Default::default() }, Default::default(), ); assert_module_map(&mut runtime2, &modules); modules.extend((200..400).map(|i| create_module(&mut runtime2, i, false))); modules.push(create_module(&mut runtime2, 400, true)); assert_module_map(&mut runtime2, &modules); let snapshot2 = runtime2.snapshot(); let mut runtime3 = JsRuntime::new(RuntimeOptions { module_loader: Some(loader), startup_snapshot: Some(Snapshot::JustCreated(snapshot2)), extensions: vec![Extension::builder("text_ext") .ops(vec![op_test::decl()]) .build()], ..Default::default() }); assert_module_map(&mut runtime3, &modules); let source_code = r#"(async () => { const mod = await import("file:///400.js"); return mod.f400() + " " + Deno.core.ops.op_test(); })();"#; let val = runtime3.execute_script_static(".", source_code).unwrap(); let val = futures::executor::block_on(runtime3.resolve_value(val)).unwrap(); { let scope = &mut runtime3.handle_scope(); let value = v8::Local::new(scope, val); let str_ = value.to_string(scope).unwrap().to_rust_string_lossy(scope); assert_eq!(str_, "hello world test"); } } #[test] fn test_error_without_stack() { let mut runtime = JsRuntime::new(RuntimeOptions::default()); // SyntaxError let result = runtime.execute_script_static( "error_without_stack.js", r#" function main() { console.log("asdf); } main(); "#, ); let expected_error = r#"Uncaught SyntaxError: Invalid or unexpected token at error_without_stack.js:3:15"#; assert_eq!(result.unwrap_err().to_string(), expected_error); } #[test] fn test_error_stack() { let mut runtime = JsRuntime::new(RuntimeOptions::default()); let result = runtime.execute_script_static( "error_stack.js", r#" function assert(cond) { if (!cond) { throw Error("assert"); } } function main() { assert(false); } main(); "#, ); let expected_error = r#"Error: assert at assert (error_stack.js:4:11) at main (error_stack.js:8:3) at error_stack.js:10:1"#; assert_eq!(result.unwrap_err().to_string(), expected_error); } #[tokio::test] async fn test_error_async_stack() { let mut runtime = JsRuntime::new(RuntimeOptions::default()); poll_fn(move |cx| { runtime .execute_script_static( "error_async_stack.js", r#" (async () => { const p = (async () => { await Promise.resolve().then(() => { throw new Error("async"); }); })(); try { await p; } catch (error) { console.log(error.stack); throw error; } })();"#, ) .unwrap(); let expected_error = r#"Error: async at error_async_stack.js:5:13 at async error_async_stack.js:4:5 at async error_async_stack.js:9:5"#; match runtime.poll_event_loop(cx, false) { Poll::Ready(Err(e)) => { assert_eq!(e.to_string(), expected_error); } _ => panic!(), }; Poll::Ready(()) }) .await; } #[tokio::test] async fn test_error_context() { use anyhow::anyhow; #[op] fn op_err_sync() -> Result<(), Error> { Err(anyhow!("original sync error").context("higher-level sync error")) } #[op] async fn op_err_async() -> Result<(), Error> { Err(anyhow!("original async error").context("higher-level async error")) } deno_core::extension!(test_ext, ops = [op_err_sync, op_err_async]); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![test_ext::init_ops()], ..Default::default() }); poll_fn(move |cx| { runtime .execute_script_static( "test_error_context_sync.js", r#" let errMessage; try { Deno.core.ops.op_err_sync(); } catch (err) { errMessage = err.message; } if (errMessage !== "higher-level sync error: original sync error") { throw new Error("unexpected error message from op_err_sync: " + errMessage); } "#, ) .unwrap(); let promise = runtime .execute_script_static( "test_error_context_async.js", r#" (async () => { let errMessage; try { await Deno.core.opAsync("op_err_async"); } catch (err) { errMessage = err.message; } if (errMessage !== "higher-level async error: original async error") { throw new Error("unexpected error message from op_err_async: " + errMessage); } })() "#, ) .unwrap(); match runtime.poll_value(&promise, cx) { Poll::Ready(Ok(_)) => {} Poll::Ready(Err(err)) => panic!("{err:?}"), _ => panic!(), } Poll::Ready(()) }) .await; } #[tokio::test] async fn test_pump_message_loop() { let mut runtime = JsRuntime::new(RuntimeOptions::default()); poll_fn(move |cx| { runtime .execute_script_static( "pump_message_loop.js", r#" function assertEquals(a, b) { if (a === b) return; throw a + " does not equal " + b; } const sab = new SharedArrayBuffer(16); const i32a = new Int32Array(sab); globalThis.resolved = false; (function() { const result = Atomics.waitAsync(i32a, 0, 0); result.value.then( (value) => { assertEquals("ok", value); globalThis.resolved = true; }, () => { assertUnreachable(); }); })(); const notify_return_value = Atomics.notify(i32a, 0, 1); assertEquals(1, notify_return_value); "#, ) .unwrap(); match runtime.poll_event_loop(cx, false) { Poll::Ready(Ok(())) => {} _ => panic!(), }; // noop script, will resolve promise from first script runtime .execute_script_static("pump_message_loop2.js", r#"assertEquals(1, 1);"#) .unwrap(); // check that promise from `Atomics.waitAsync` has been resolved runtime .execute_script_static( "pump_message_loop3.js", r#"assertEquals(globalThis.resolved, true);"#, ) .unwrap(); Poll::Ready(()) }) .await; } #[test] fn test_v8_platform() { let options = RuntimeOptions { v8_platform: Some(v8::new_default_platform(0, false).make_shared()), ..Default::default() }; let mut runtime = JsRuntime::new(options); runtime.execute_script_static("", "").unwrap(); } #[ignore] // TODO(@littledivy): Fast API ops when snapshot is not loaded. #[test] fn test_is_proxy() { let mut runtime = JsRuntime::new(RuntimeOptions::default()); let all_true: v8::Global = runtime .execute_script_static( "is_proxy.js", r#" (function () { const o = { a: 1, b: 2}; const p = new Proxy(o, {}); return Deno.core.ops.op_is_proxy(p) && !Deno.core.ops.op_is_proxy(o) && !Deno.core.ops.op_is_proxy(42); })() "#, ) .unwrap(); let mut scope = runtime.handle_scope(); let all_true = v8::Local::::new(&mut scope, &all_true); assert!(all_true.is_true()); } #[tokio::test] async fn test_async_opstate_borrow() { struct InnerState(u64); #[op] async fn op_async_borrow( op_state: Rc>, ) -> Result<(), Error> { let n = { let op_state = op_state.borrow(); let inner_state = op_state.borrow::(); inner_state.0 }; // Future must be Poll::Pending on first call tokio::time::sleep(std::time::Duration::from_millis(1)).await; if n != 42 { unreachable!(); } Ok(()) } deno_core::extension!( test_ext, ops = [op_async_borrow], state = |state| state.put(InnerState(42)) ); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![test_ext::init_ops()], ..Default::default() }); runtime .execute_script_static( "op_async_borrow.js", "Deno.core.opAsync(\"op_async_borrow\")", ) .unwrap(); runtime.run_event_loop(false).await.unwrap(); } #[tokio::test] async fn test_sync_op_serialize_object_with_numbers_as_keys() { #[op] fn op_sync_serialize_object_with_numbers_as_keys( value: serde_json::Value, ) -> Result<(), Error> { assert_eq!( value.to_string(), r#"{"lines":{"100":{"unit":"m"},"200":{"unit":"cm"}}}"# ); Ok(()) } deno_core::extension!( test_ext, ops = [op_sync_serialize_object_with_numbers_as_keys] ); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![test_ext::init_ops()], ..Default::default() }); runtime .execute_script_static( "op_sync_serialize_object_with_numbers_as_keys.js", r#" Deno.core.ops.op_sync_serialize_object_with_numbers_as_keys({ lines: { 100: { unit: "m" }, 200: { unit: "cm" } } }) "#, ) .unwrap(); runtime.run_event_loop(false).await.unwrap(); } #[tokio::test] async fn test_async_op_serialize_object_with_numbers_as_keys() { #[op] async fn op_async_serialize_object_with_numbers_as_keys( value: serde_json::Value, ) -> Result<(), Error> { assert_eq!( value.to_string(), r#"{"lines":{"100":{"unit":"m"},"200":{"unit":"cm"}}}"# ); Ok(()) } deno_core::extension!( test_ext, ops = [op_async_serialize_object_with_numbers_as_keys] ); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![test_ext::init_ops()], ..Default::default() }); runtime .execute_script_static( "op_async_serialize_object_with_numbers_as_keys.js", r#" Deno.core.opAsync("op_async_serialize_object_with_numbers_as_keys", { lines: { 100: { unit: "m" }, 200: { unit: "cm" } } }) "#, ) .unwrap(); runtime.run_event_loop(false).await.unwrap(); } #[tokio::test] async fn test_set_macrotask_callback_set_next_tick_callback() { #[op] async fn op_async_sleep() -> Result<(), Error> { // Future must be Poll::Pending on first call tokio::time::sleep(std::time::Duration::from_millis(1)).await; Ok(()) } deno_core::extension!(test_ext, ops = [op_async_sleep]); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![test_ext::init_ops()], ..Default::default() }); runtime .execute_script_static( "macrotasks_and_nextticks.js", r#" (async function () { const results = []; Deno.core.setMacrotaskCallback(() => { results.push("macrotask"); return true; }); Deno.core.setNextTickCallback(() => { results.push("nextTick"); Deno.core.ops.op_set_has_tick_scheduled(false); }); Deno.core.ops.op_set_has_tick_scheduled(true); await Deno.core.opAsync('op_async_sleep'); if (results[0] != "nextTick") { throw new Error(`expected nextTick, got: ${results[0]}`); } if (results[1] != "macrotask") { throw new Error(`expected macrotask, got: ${results[1]}`); } })(); "#, ) .unwrap(); runtime.run_event_loop(false).await.unwrap(); } #[test] fn test_has_tick_scheduled() { use futures::task::ArcWake; static MACROTASK: AtomicUsize = AtomicUsize::new(0); static NEXT_TICK: AtomicUsize = AtomicUsize::new(0); #[op] fn op_macrotask() -> Result<(), AnyError> { MACROTASK.fetch_add(1, Ordering::Relaxed); Ok(()) } #[op] fn op_next_tick() -> Result<(), AnyError> { NEXT_TICK.fetch_add(1, Ordering::Relaxed); Ok(()) } deno_core::extension!(test_ext, ops = [op_macrotask, op_next_tick]); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![test_ext::init_ops()], ..Default::default() }); runtime .execute_script_static( "has_tick_scheduled.js", r#" Deno.core.setMacrotaskCallback(() => { Deno.core.ops.op_macrotask(); return true; // We're done. }); Deno.core.setNextTickCallback(() => Deno.core.ops.op_next_tick()); Deno.core.ops.op_set_has_tick_scheduled(true); "#, ) .unwrap(); struct ArcWakeImpl(Arc); impl ArcWake for ArcWakeImpl { fn wake_by_ref(arc_self: &Arc) { arc_self.0.fetch_add(1, Ordering::Relaxed); } } let awoken_times = Arc::new(AtomicUsize::new(0)); let waker = futures::task::waker(Arc::new(ArcWakeImpl(awoken_times.clone()))); let cx = &mut Context::from_waker(&waker); assert!(matches!(runtime.poll_event_loop(cx, false), Poll::Pending)); assert_eq!(1, MACROTASK.load(Ordering::Relaxed)); assert_eq!(1, NEXT_TICK.load(Ordering::Relaxed)); assert_eq!(awoken_times.swap(0, Ordering::Relaxed), 1); assert!(matches!(runtime.poll_event_loop(cx, false), Poll::Pending)); assert_eq!(awoken_times.swap(0, Ordering::Relaxed), 1); assert!(matches!(runtime.poll_event_loop(cx, false), Poll::Pending)); assert_eq!(awoken_times.swap(0, Ordering::Relaxed), 1); assert!(matches!(runtime.poll_event_loop(cx, false), Poll::Pending)); assert_eq!(awoken_times.swap(0, Ordering::Relaxed), 1); runtime.inner.state.borrow_mut().has_tick_scheduled = false; assert!(matches!( runtime.poll_event_loop(cx, false), Poll::Ready(Ok(())) )); assert_eq!(awoken_times.load(Ordering::Relaxed), 0); assert!(matches!( runtime.poll_event_loop(cx, false), Poll::Ready(Ok(())) )); assert_eq!(awoken_times.load(Ordering::Relaxed), 0); } #[test] fn terminate_during_module_eval() { #[derive(Default)] struct ModsLoader; impl ModuleLoader for ModsLoader { fn resolve( &self, specifier: &str, referrer: &str, _kind: ResolutionKind, ) -> Result { assert_eq!(specifier, "file:///main.js"); assert_eq!(referrer, "."); let s = crate::resolve_import(specifier, referrer).unwrap(); Ok(s) } fn load( &self, _module_specifier: &ModuleSpecifier, _maybe_referrer: Option<&ModuleSpecifier>, _is_dyn_import: bool, ) -> Pin> { async move { Ok(ModuleSource::for_test( "console.log('hello world');", "file:///main.js", )) } .boxed_local() } } let loader = std::rc::Rc::new(ModsLoader::default()); let mut runtime = JsRuntime::new(RuntimeOptions { module_loader: Some(loader), ..Default::default() }); let specifier = crate::resolve_url("file:///main.js").unwrap(); let source_code = ascii_str!("Deno.core.print('hello\\n')"); let module_id = futures::executor::block_on( runtime.load_main_module(&specifier, Some(source_code)), ) .unwrap(); runtime.v8_isolate().terminate_execution(); let mod_result = futures::executor::block_on(runtime.mod_evaluate(module_id)).unwrap(); assert!(mod_result .unwrap_err() .to_string() .contains("JavaScript execution has been terminated")); } #[tokio::test] async fn test_unhandled_rejection_order() { let mut runtime = JsRuntime::new(Default::default()); runtime .execute_script_static( "", r#" for (let i = 0; i < 100; i++) { Promise.reject(i); } "#, ) .unwrap(); let err = runtime.run_event_loop(false).await.unwrap_err(); assert_eq!(err.to_string(), "Uncaught (in promise) 0"); } #[tokio::test] async fn test_set_promise_reject_callback() { static PROMISE_REJECT: AtomicUsize = AtomicUsize::new(0); #[op] fn op_promise_reject() -> Result<(), AnyError> { PROMISE_REJECT.fetch_add(1, Ordering::Relaxed); Ok(()) } deno_core::extension!(test_ext, ops = [op_promise_reject]); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![test_ext::init_ops()], ..Default::default() }); runtime .execute_script_static( "promise_reject_callback.js", r#" // Note: |promise| is not the promise created below, it's a child. Deno.core.ops.op_set_promise_reject_callback((type, promise, reason) => { if (type !== /* PromiseRejectWithNoHandler */ 0) { throw Error("unexpected type: " + type); } if (reason.message !== "reject") { throw Error("unexpected reason: " + reason); } Deno.core.ops.op_store_pending_promise_rejection(promise); Deno.core.ops.op_promise_reject(); }); new Promise((_, reject) => reject(Error("reject"))); "#, ) .unwrap(); runtime.run_event_loop(false).await.unwrap_err(); assert_eq!(1, PROMISE_REJECT.load(Ordering::Relaxed)); runtime .execute_script_static( "promise_reject_callback.js", r#" { const prev = Deno.core.ops.op_set_promise_reject_callback((...args) => { prev(...args); }); } new Promise((_, reject) => reject(Error("reject"))); "#, ) .unwrap(); runtime.run_event_loop(false).await.unwrap_err(); assert_eq!(2, PROMISE_REJECT.load(Ordering::Relaxed)); } #[tokio::test] async fn test_set_promise_reject_callback_realms() { let mut runtime = JsRuntime::new(RuntimeOptions::default()); let global_realm = runtime.global_realm(); let realm1 = runtime.create_realm().unwrap(); let realm2 = runtime.create_realm().unwrap(); let realm_expectations = &[ (&global_realm, "global_realm", 42), (&realm1, "realm1", 140), (&realm2, "realm2", 720), ]; // Set up promise reject callbacks. for (realm, realm_name, number) in realm_expectations { realm .execute_script( runtime.v8_isolate(), "", format!( r#" globalThis.rejectValue = undefined; Deno.core.setPromiseRejectCallback((_type, _promise, reason) => {{ globalThis.rejectValue = `{realm_name}/${{reason}}`; }}); Deno.core.opAsync("op_void_async").then(() => Promise.reject({number})); "# ).into() ) .unwrap(); } runtime.run_event_loop(false).await.unwrap(); for (realm, realm_name, number) in realm_expectations { let reject_value = realm .execute_script_static(runtime.v8_isolate(), "", "globalThis.rejectValue") .unwrap(); let scope = &mut realm.handle_scope(runtime.v8_isolate()); let reject_value = v8::Local::new(scope, reject_value); assert!(reject_value.is_string()); let reject_value_string = reject_value.to_rust_string_lossy(scope); assert_eq!(reject_value_string, format!("{realm_name}/{number}")); } } #[tokio::test] async fn test_set_promise_reject_callback_top_level_await() { static PROMISE_REJECT: AtomicUsize = AtomicUsize::new(0); #[op] fn op_promise_reject() -> Result<(), AnyError> { PROMISE_REJECT.fetch_add(1, Ordering::Relaxed); Ok(()) } deno_core::extension!(test_ext, ops = [op_promise_reject]); #[derive(Default)] struct ModsLoader; impl ModuleLoader for ModsLoader { fn resolve( &self, specifier: &str, referrer: &str, _kind: ResolutionKind, ) -> Result { assert_eq!(specifier, "file:///main.js"); assert_eq!(referrer, "."); let s = crate::resolve_import(specifier, referrer).unwrap(); Ok(s) } fn load( &self, _module_specifier: &ModuleSpecifier, _maybe_referrer: Option<&ModuleSpecifier>, _is_dyn_import: bool, ) -> Pin> { let code = r#" Deno.core.ops.op_set_promise_reject_callback((type, promise, reason) => { Deno.core.ops.op_promise_reject(); }); throw new Error('top level throw'); "#; async move { Ok(ModuleSource::for_test(code, "file:///main.js")) } .boxed_local() } } let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![test_ext::init_ops()], module_loader: Some(Rc::new(ModsLoader)), ..Default::default() }); let id = runtime .load_main_module(&crate::resolve_url("file:///main.js").unwrap(), None) .await .unwrap(); let receiver = runtime.mod_evaluate(id); runtime.run_event_loop(false).await.unwrap(); receiver.await.unwrap().unwrap_err(); assert_eq!(1, PROMISE_REJECT.load(Ordering::Relaxed)); } #[test] fn test_op_return_serde_v8_error() { #[op] fn op_err() -> Result, anyhow::Error> { Ok([(1, 2), (3, 4)].into_iter().collect()) // Maps can't have non-string keys in serde_v8 } deno_core::extension!(test_ext, ops = [op_err]); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![test_ext::init_ops()], ..Default::default() }); assert!(runtime .execute_script_static( "test_op_return_serde_v8_error.js", "Deno.core.ops.op_err()" ) .is_err()); } #[test] fn test_op_high_arity() { #[op] fn op_add_4( x1: i64, x2: i64, x3: i64, x4: i64, ) -> Result { Ok(x1 + x2 + x3 + x4) } deno_core::extension!(test_ext, ops = [op_add_4]); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![test_ext::init_ops()], ..Default::default() }); let r = runtime .execute_script_static("test.js", "Deno.core.ops.op_add_4(1, 2, 3, 4)") .unwrap(); let scope = &mut runtime.handle_scope(); assert_eq!(r.open(scope).integer_value(scope), Some(10)); } #[test] fn test_op_disabled() { #[op] fn op_foo() -> Result { Ok(42) } fn ops() -> Vec { vec![op_foo::decl().disable()] } deno_core::extension!(test_ext, ops_fn = ops); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![test_ext::init_ops()], ..Default::default() }); let err = runtime .execute_script_static("test.js", "Deno.core.ops.op_foo()") .unwrap_err(); assert!(err .to_string() .contains("TypeError: Deno.core.ops.op_foo is not a function")); } #[test] fn test_op_detached_buffer() { use serde_v8::DetachedBuffer; #[op] fn op_sum_take(b: DetachedBuffer) -> Result { Ok(b.as_ref().iter().clone().map(|x| *x as u64).sum()) } #[op] fn op_boomerang(b: DetachedBuffer) -> Result { Ok(b) } deno_core::extension!(test_ext, ops = [op_sum_take, op_boomerang]); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![test_ext::init_ops()], ..Default::default() }); runtime .execute_script_static( "test.js", r#" const a1 = new Uint8Array([1,2,3]); const a1b = a1.subarray(0, 3); const a2 = new Uint8Array([5,10,15]); const a2b = a2.subarray(0, 3); if (!(a1.length > 0 && a1b.length > 0)) { throw new Error("a1 & a1b should have a length"); } let sum = Deno.core.ops.op_sum_take(a1b); if (sum !== 6) { throw new Error(`Bad sum: ${sum}`); } if (a1.length > 0 || a1b.length > 0) { throw new Error("expecting a1 & a1b to be detached"); } const a3 = Deno.core.ops.op_boomerang(a2b); if (a3.byteLength != 3) { throw new Error(`Expected a3.byteLength === 3, got ${a3.byteLength}`); } if (a3[0] !== 5 || a3[1] !== 10) { throw new Error(`Invalid a3: ${a3[0]}, ${a3[1]}`); } if (a2.byteLength > 0 || a2b.byteLength > 0) { throw new Error("expecting a2 & a2b to be detached, a3 re-attached"); } const wmem = new WebAssembly.Memory({ initial: 1, maximum: 2 }); const w32 = new Uint32Array(wmem.buffer); w32[0] = 1; w32[1] = 2; w32[2] = 3; const assertWasmThrow = (() => { try { let sum = Deno.core.ops.op_sum_take(w32.subarray(0, 2)); return false; } catch(e) { return e.message.includes('invalid type; expected: detachable'); } }); if (!assertWasmThrow()) { throw new Error("expected wasm mem to not be detachable"); } "#, ) .unwrap(); } #[test] fn test_op_unstable_disabling() { #[op] fn op_foo() -> Result { Ok(42) } #[op(unstable)] fn op_bar() -> Result { Ok(42) } deno_core::extension!( test_ext, ops = [op_foo, op_bar], middleware = |op| if op.is_unstable { op.disable() } else { op } ); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![test_ext::init_ops()], ..Default::default() }); runtime .execute_script_static( "test.js", r#" if (Deno.core.ops.op_foo() !== 42) { throw new Error("Exptected op_foo() === 42"); } if (typeof Deno.core.ops.op_bar !== "undefined") { throw new Error("Expected op_bar to be disabled") } "#, ) .unwrap(); } #[test] fn js_realm_simple() { let mut runtime = JsRuntime::new(Default::default()); let main_context = runtime.global_context(); let main_global = { let scope = &mut runtime.handle_scope(); let local_global = main_context.open(scope).global(scope); v8::Global::new(scope, local_global) }; let realm = runtime.create_realm().unwrap(); assert_ne!(realm.context(), &main_context); assert_ne!(realm.global_object(runtime.v8_isolate()), main_global); let main_object = runtime.execute_script_static("", "Object").unwrap(); let realm_object = realm .execute_script_static(runtime.v8_isolate(), "", "Object") .unwrap(); assert_ne!(main_object, realm_object); } #[test] fn js_realm_init() { #[op] fn op_test() -> Result { Ok(String::from("Test")) } deno_core::extension!(test_ext, ops = [op_test]); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![test_ext::init_ops()], ..Default::default() }); let realm = runtime.create_realm().unwrap(); let ret = realm .execute_script_static(runtime.v8_isolate(), "", "Deno.core.ops.op_test()") .unwrap(); let scope = &mut realm.handle_scope(runtime.v8_isolate()); assert_eq!(ret, serde_v8::to_v8(scope, "Test").unwrap()); } #[test] fn js_realm_init_snapshot() { let snapshot = { let runtime = JsRuntimeForSnapshot::new(Default::default(), Default::default()); let snap: &[u8] = &runtime.snapshot(); Vec::from(snap).into_boxed_slice() }; #[op] fn op_test() -> Result { Ok(String::from("Test")) } deno_core::extension!(test_ext, ops = [op_test]); let mut runtime = JsRuntime::new(RuntimeOptions { startup_snapshot: Some(Snapshot::Boxed(snapshot)), extensions: vec![test_ext::init_ops()], ..Default::default() }); let realm = runtime.create_realm().unwrap(); let ret = realm .execute_script_static(runtime.v8_isolate(), "", "Deno.core.ops.op_test()") .unwrap(); let scope = &mut realm.handle_scope(runtime.v8_isolate()); assert_eq!(ret, serde_v8::to_v8(scope, "Test").unwrap()); } #[test] fn js_realm_sync_ops() { // Test that returning a RustToV8Buf and throwing an exception from a sync // op result in objects with prototypes from the right realm. Note that we // don't test the result of returning structs, because they will be // serialized to objects with null prototype. #[op] fn op_test(fail: bool) -> Result { if !fail { Ok(ToJsBuffer::empty()) } else { Err(crate::error::type_error("Test")) } } deno_core::extension!(test_ext, ops = [op_test]); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![test_ext::init_ops()], get_error_class_fn: Some(&|error| { crate::error::get_custom_error_class(error).unwrap() }), ..Default::default() }); let new_realm = runtime.create_realm().unwrap(); // Test in both realms for realm in [runtime.global_realm(), new_realm].into_iter() { let ret = realm .execute_script_static( runtime.v8_isolate(), "", r#" const buf = Deno.core.ops.op_test(false); try { Deno.core.ops.op_test(true); } catch(e) { err = e; } buf instanceof Uint8Array && buf.byteLength === 0 && err instanceof TypeError && err.message === "Test" "#, ) .unwrap(); assert!(ret.open(runtime.v8_isolate()).is_true()); } } #[tokio::test] async fn js_realm_async_ops() { // Test that returning a RustToV8Buf and throwing an exception from a async // op result in objects with prototypes from the right realm. Note that we // don't test the result of returning structs, because they will be // serialized to objects with null prototype. #[op] async fn op_test(fail: bool) -> Result { if !fail { Ok(ToJsBuffer::empty()) } else { Err(crate::error::type_error("Test")) } } deno_core::extension!(test_ext, ops = [op_test]); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![test_ext::init_ops()], get_error_class_fn: Some(&|error| { crate::error::get_custom_error_class(error).unwrap() }), ..Default::default() }); let global_realm = runtime.global_realm(); let new_realm = runtime.create_realm().unwrap(); let mut rets = vec![]; // Test in both realms for realm in [global_realm, new_realm].into_iter() { let ret = realm .execute_script_static( runtime.v8_isolate(), "", r#" (async function () { const buf = await Deno.core.opAsync("op_test", false); let err; try { await Deno.core.opAsync("op_test", true); } catch(e) { err = e; } return buf instanceof Uint8Array && buf.byteLength === 0 && err instanceof TypeError && err.message === "Test" ; })(); "#, ) .unwrap(); rets.push((realm, ret)); } runtime.run_event_loop(false).await.unwrap(); for ret in rets { let scope = &mut ret.0.handle_scope(runtime.v8_isolate()); let value = v8::Local::new(scope, ret.1); let promise = v8::Local::::try_from(value).unwrap(); let result = promise.result(scope); assert!(result.is_boolean() && result.is_true()); } } #[ignore] #[tokio::test] async fn js_realm_gc() { static INVOKE_COUNT: AtomicUsize = AtomicUsize::new(0); struct PendingFuture {} impl Future for PendingFuture { type Output = (); fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> { Poll::Pending } } impl Drop for PendingFuture { fn drop(&mut self) { assert_eq!(INVOKE_COUNT.fetch_sub(1, Ordering::SeqCst), 1); } } // Never resolves. #[op] async fn op_pending() { assert_eq!(INVOKE_COUNT.fetch_add(1, Ordering::SeqCst), 0); PendingFuture {}.await } deno_core::extension!(test_ext, ops = [op_pending]); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![test_ext::init_ops()], ..Default::default() }); // Detect a drop in OpState let opstate_drop_detect = Rc::new(()); runtime .op_state() .borrow_mut() .put(opstate_drop_detect.clone()); assert_eq!(Rc::strong_count(&opstate_drop_detect), 2); let other_realm = runtime.create_realm().unwrap(); other_realm .execute_script( runtime.v8_isolate(), "future", ModuleCode::from_static("Deno.core.opAsync('op_pending')"), ) .unwrap(); while INVOKE_COUNT.load(Ordering::SeqCst) == 0 { poll_fn(|cx: &mut Context| runtime.poll_event_loop(cx, false)) .await .unwrap(); } drop(other_realm); while INVOKE_COUNT.load(Ordering::SeqCst) == 1 { poll_fn(|cx| runtime.poll_event_loop(cx, false)) .await .unwrap(); } drop(runtime); // Make sure the OpState was dropped properly when the runtime dropped assert_eq!(Rc::strong_count(&opstate_drop_detect), 1); } #[tokio::test] async fn js_realm_ref_unref_ops() { // Never resolves. #[op] async fn op_pending() { futures::future::pending().await } deno_core::extension!(test_ext, ops = [op_pending]); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![test_ext::init_ops()], ..Default::default() }); poll_fn(move |cx| { let main_realm = runtime.global_realm(); let other_realm = runtime.create_realm().unwrap(); main_realm .execute_script_static( runtime.v8_isolate(), "", r#" var promise = Deno.core.opAsync("op_pending"); "#, ) .unwrap(); other_realm .execute_script_static( runtime.v8_isolate(), "", r#" var promise = Deno.core.opAsync("op_pending"); "#, ) .unwrap(); assert!(matches!(runtime.poll_event_loop(cx, false), Poll::Pending)); main_realm .execute_script_static( runtime.v8_isolate(), "", r#" let promiseIdSymbol = Symbol.for("Deno.core.internalPromiseId"); Deno.core.unrefOp(promise[promiseIdSymbol]); "#, ) .unwrap(); assert!(matches!(runtime.poll_event_loop(cx, false), Poll::Pending)); other_realm .execute_script_static( runtime.v8_isolate(), "", r#" let promiseIdSymbol = Symbol.for("Deno.core.internalPromiseId"); Deno.core.unrefOp(promise[promiseIdSymbol]); "#, ) .unwrap(); assert!(matches!( runtime.poll_event_loop(cx, false), Poll::Ready(Ok(())) )); Poll::Ready(()) }) .await; } #[test] fn test_array_by_copy() { // Verify that "array by copy" proposal is enabled (https://github.com/tc39/proposal-change-array-by-copy) let mut runtime = JsRuntime::new(Default::default()); assert!(runtime .execute_script_static( "test_array_by_copy.js", "const a = [1, 2, 3]; const b = a.toReversed(); if (!(a[0] === 1 && a[1] === 2 && a[2] === 3)) { throw new Error('Expected a to be intact'); } if (!(b[0] === 3 && b[1] === 2 && b[2] === 1)) { throw new Error('Expected b to be reversed'); }", ) .is_ok()); } #[cfg(debug_assertions)] #[test] #[should_panic(expected = "Found ops with duplicate names:")] fn duplicate_op_names() { mod a { use super::*; #[op] fn op_test() -> Result { Ok(String::from("Test")) } } #[op] fn op_test() -> Result { Ok(String::from("Test")) } deno_core::extension!(test_ext, ops = [a::op_test, op_test]); JsRuntime::new(RuntimeOptions { extensions: vec![test_ext::init_ops()], ..Default::default() }); } #[test] fn ops_in_js_have_proper_names() { #[op] fn op_test_sync() -> Result { Ok(String::from("Test")) } #[op] async fn op_test_async() -> Result { Ok(String::from("Test")) } deno_core::extension!(test_ext, ops = [op_test_sync, op_test_async]); let mut runtime = JsRuntime::new(RuntimeOptions { extensions: vec![test_ext::init_ops()], ..Default::default() }); let src = r#" if (Deno.core.ops.op_test_sync.name !== "op_test_sync") { throw new Error(); } if (Deno.core.ops.op_test_async.name !== "op_test_async") { throw new Error(); } const { op_test_async } = Deno.core.ensureFastOps(); if (op_test_async.name !== "op_test_async") { throw new Error(); } "#; runtime.execute_script_static("test", src).unwrap(); }