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.rs2396
1 files changed, 0 insertions, 2396 deletions
diff --git a/core/runtime/tests.rs b/core/runtime/tests.rs
deleted file mode 100644
index 663645bb1..000000000
--- a/core/runtime/tests.rs
+++ /dev/null
@@ -1,2396 +0,0 @@
-// 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 cooked_waker::IntoWaker;
-use cooked_waker::Wake;
-use cooked_waker::WakeRef;
-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::AtomicBool;
-use std::sync::atomic::AtomicI8;
-use std::sync::atomic::AtomicUsize;
-use std::sync::atomic::Ordering;
-use std::sync::Arc;
-use std::task::Context;
-use std::task::Poll;
-use std::time::Duration;
-
-// 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<JsBuffer>,
-) -> 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", 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"
- );
- }
-}
-
-#[derive(Default)]
-struct LoggingWaker {
- woken: AtomicBool,
-}
-
-impl Wake for LoggingWaker {
- fn wake(self) {
- self.woken.store(true, Ordering::SeqCst);
- }
-}
-
-impl WakeRef for LoggingWaker {
- fn wake_by_ref(&self) {
- self.woken.store(true, Ordering::SeqCst);
- }
-}
-
-/// This is a reproduction for a very obscure bug where the Deno runtime locks up we end up polling
-/// an empty JoinSet and attempt to resolve ops after-the-fact. There's a small footgun in the JoinSet
-/// API where polling it while empty returns Ready(None), which means that it never holds on to the
-/// waker. This means that if we aren't testing for this particular return value and don't stash the waker
-/// ourselves for a future async op to eventually queue, we can end up losing the waker entirely and the
-/// op wakes up, notifies tokio, which notifies the JoinSet, which then has nobody to notify )`:.
-#[tokio::test]
-async fn test_wakers_for_async_ops() {
- static STATE: AtomicI8 = AtomicI8::new(0);
-
- #[op]
- async fn op_async_sleep() -> Result<(), Error> {
- STATE.store(1, Ordering::SeqCst);
- tokio::time::sleep(std::time::Duration::from_millis(1)).await;
- STATE.store(2, Ordering::SeqCst);
- Ok(())
- }
-
- STATE.store(0, Ordering::SeqCst);
-
- let logging_waker = Arc::new(LoggingWaker::default());
- let waker = logging_waker.clone().into_waker();
-
- deno_core::extension!(test_ext, ops = [op_async_sleep]);
- let mut runtime = JsRuntime::new(RuntimeOptions {
- extensions: vec![test_ext::init_ops()],
- ..Default::default()
- });
-
- // Drain events until we get to Ready
- loop {
- logging_waker.woken.store(false, Ordering::SeqCst);
- let res = runtime.poll_event_loop(&mut Context::from_waker(&waker), false);
- let ready = matches!(res, Poll::Ready(Ok(())));
- assert!(ready || logging_waker.woken.load(Ordering::SeqCst));
- if ready {
- break;
- }
- }
-
- // Start the AIIFE
- runtime
- .execute_script(
- "",
- FastString::from_static(
- "(async () => { await Deno.core.opAsync('op_async_sleep'); })()",
- ),
- )
- .unwrap();
-
- // Wait for future to finish
- while STATE.load(Ordering::SeqCst) < 2 {
- tokio::time::sleep(Duration::from_millis(1)).await;
- }
-
- // This shouldn't take one minute, but if it does, things are definitely locked up
- for _ in 0..Duration::from_secs(60).as_millis() {
- if logging_waker.woken.load(Ordering::SeqCst) {
- // Success
- return;
- }
- tokio::time::sleep(Duration::from_millis(1)).await;
- }
-
- panic!("The waker was never woken after the future completed");
-}
-
-#[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 WebAssembly 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("Expected 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 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<ToJsBuffer, Error> {
- 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<ToJsBuffer, Error> {
- 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::<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();
-}