summaryrefslogtreecommitdiff
path: root/core/runtime/tests.rs
diff options
context:
space:
mode:
Diffstat (limited to 'core/runtime/tests.rs')
-rw-r--r--core/runtime/tests.rs2306
1 files changed, 2306 insertions, 0 deletions
diff --git a/core/runtime/tests.rs b/core/runtime/tests.rs
new file mode 100644
index 000000000..857290b80
--- /dev/null
+++ b/core/runtime/tests.rs
@@ -0,0 +1,2306 @@
+// 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::ZeroCopyBuf;
+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<AtomicUsize>,
+}
+
+#[op]
+async fn op_test(
+ rc_op_state: Rc<RefCell<OpState>>,
+ control: u8,
+ buf: Option<ZeroCopyBuf>,
+) -> Result<u8, AnyError> {
+ #![allow(clippy::await_holding_refcell_ref)] // False positive.
+ let op_state_ = rc_op_state.borrow();
+ let test_state = op_state_.borrow::<TestState>();
+ 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<AtomicUsize>) {
+ let dispatch_count = Arc::new(AtomicUsize::new(0));
+ deno_core::extension!(
+ test_ext,
+ ops = [op_test],
+ options = {
+ mode: Mode,
+ dispatch_count: Arc<AtomicUsize>,
+ },
+ 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");
+ "#,
+ )
+ .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(null, 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::<JsError>().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::<JsError>().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::<JsError>().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<ModuleSpecifier, Error> {
+ 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<Box<ModuleSourceFuture>> {
+ 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::<v8::Object>::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::<JsError>().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::<JsError>().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<ModuleSpecifier, Error> {
+ 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<Box<ModuleSourceFuture>> {
+ 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<ModuleInfo>) {
+ 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<String, Error> {
+ 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("<none>", "").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<v8::Value> = 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::<v8::Value>::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<RefCell<OpState>>,
+ ) -> Result<(), Error> {
+ let n = {
+ let op_state = op_state.borrow();
+ let inner_state = op_state.borrow::<InnerState>();
+ 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<AtomicUsize>);
+ impl ArcWake for ArcWakeImpl {
+ fn wake_by_ref(arc_self: &Arc<Self>) {
+ 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<ModuleSpecifier, Error> {
+ 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<Box<ModuleSourceFuture>> {
+ 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<ModuleSpecifier, Error> {
+ 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<Box<ModuleSourceFuture>> {
+ 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<std::collections::BTreeMap<u64, u64>, 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<i64, anyhow::Error> {
+ 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<i64, anyhow::Error> {
+ Ok(42)
+ }
+
+ fn ops() -> Vec<OpDecl> {
+ 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<u64, anyhow::Error> {
+ Ok(b.as_ref().iter().clone().map(|x| *x as u64).sum())
+ }
+
+ #[op]
+ fn op_boomerang(b: DetachedBuffer) -> Result<DetachedBuffer, anyhow::Error> {
+ 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<i64, anyhow::Error> {
+ Ok(42)
+ }
+
+ #[op(unstable)]
+ fn op_bar() -> Result<i64, anyhow::Error> {
+ 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<String, Error> {
+ 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<String, Error> {
+ 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 ZeroCopyBuf 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<ZeroCopyBuf, Error> {
+ if !fail {
+ Ok(ZeroCopyBuf::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 ZeroCopyBuf 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<ZeroCopyBuf, Error> {
+ if !fail {
+ Ok(ZeroCopyBuf::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::<v8::Promise>::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<String, Error> {
+ Ok(String::from("Test"))
+ }
+ }
+
+ #[op]
+ fn op_test() -> Result<String, Error> {
+ 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<String, Error> {
+ Ok(String::from("Test"))
+ }
+
+ #[op]
+ async fn op_test_async() -> Result<String, Error> {
+ 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();
+}