diff --git a/cli/tests/integration/test_tests.rs b/cli/tests/integration/test_tests.rs index d9d1eed45b..0baf944223 100644 --- a/cli/tests/integration/test_tests.rs +++ b/cli/tests/integration/test_tests.rs @@ -61,6 +61,12 @@ itest!(load_unload { output: "test/load_unload.out", }); +itest!(interval { + args: "test test/interval.ts", + exit_code: 0, + output: "test/interval.out", +}); + itest!(doc { args: "test --doc --allow-all test/doc.ts", exit_code: 1, diff --git a/cli/tests/testdata/test/interval.out b/cli/tests/testdata/test/interval.out new file mode 100644 index 0000000000..024698ddec --- /dev/null +++ b/cli/tests/testdata/test/interval.out @@ -0,0 +1,5 @@ +Check [WILDCARD]/test/interval.ts +running 0 tests from [WILDCARD]/test/interval.ts + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ([WILDCARD]) + diff --git a/cli/tests/testdata/test/interval.ts b/cli/tests/testdata/test/interval.ts new file mode 100644 index 0000000000..7eb588c594 --- /dev/null +++ b/cli/tests/testdata/test/interval.ts @@ -0,0 +1 @@ +setInterval(function () {}, 0); diff --git a/cli/tests/testdata/test/unresolved_promise.out b/cli/tests/testdata/test/unresolved_promise.out index cd505bcbfc..4e5c20a3bc 100644 --- a/cli/tests/testdata/test/unresolved_promise.out +++ b/cli/tests/testdata/test/unresolved_promise.out @@ -1,6 +1,5 @@ Check [WILDCARD]/test/unresolved_promise.ts -running 2 tests from [WILDCARD]/test/unresolved_promise.ts -test unresolved promise ... -test result: FAILED. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ([WILDCARD]) + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ([WILDCARD]) error: Module evaluation is still pending but there are no pending ops or dynamic imports. This situation is often caused by unresolved promise. diff --git a/cli/tests/testdata/test/unresolved_promise.ts b/cli/tests/testdata/test/unresolved_promise.ts index 8f50e907a4..25fe707624 100644 --- a/cli/tests/testdata/test/unresolved_promise.ts +++ b/cli/tests/testdata/test/unresolved_promise.ts @@ -1,11 +1 @@ -Deno.test({ - name: "unresolved promise", - fn() { - return new Promise((_resolve, _reject) => {}); - }, -}); - -Deno.test({ - name: "ok", - fn() {}, -}); +await new Promise((_resolve, _reject) => {}); diff --git a/cli/tools/test.rs b/cli/tools/test.rs index 819f9980ca..859e239347 100644 --- a/cli/tools/test.rs +++ b/cli/tools/test.rs @@ -11,6 +11,7 @@ use crate::flags::Flags; use crate::fs_util::collect_specifiers; use crate::fs_util::is_supported_test_ext; use crate::fs_util::is_supported_test_path; +use crate::located_script_name; use crate::media_type::MediaType; use crate::module_graph; use crate::module_graph::GraphBuilder; @@ -264,21 +265,6 @@ async fn test_specifier( test_source.push_str(&format!("import \"{}\";\n", specifier)); } - test_source - .push_str("await new Promise(resolve => setTimeout(resolve, 0));\n"); - - test_source.push_str("window.dispatchEvent(new Event('load'));\n"); - - test_source.push_str(&format!( - "await Deno[Deno.internal].runTests({});\n", - json!({ - "filter": filter, - "shuffle": shuffle, - }), - )); - - test_source.push_str("window.dispatchEvent(new Event('unload'));\n"); - let test_file = File { local: test_specifier.to_file_path().unwrap(), maybe_types: None, @@ -323,9 +309,28 @@ async fn test_specifier( worker.execute_module(&test_specifier).await?; - worker - .run_event_loop(maybe_coverage_collector.is_none()) - .await?; + worker.js_runtime.execute_script( + &located_script_name!(), + "window.dispatchEvent(new Event('load'));", + )?; + + let test_result = worker.js_runtime.execute_script( + &located_script_name!(), + &format!( + r#"Deno[Deno.internal].runTests({})"#, + json!({ + "filter": filter, + "shuffle": shuffle, + }), + ), + )?; + + worker.js_runtime.resolve_value(test_result).await?; + + worker.js_runtime.execute_script( + &located_script_name!(), + "window.dispatchEvent(new Event('unload'));", + )?; if let Some(coverage_collector) = maybe_coverage_collector.as_mut() { worker diff --git a/core/runtime.rs b/core/runtime.rs index 8edd0466c6..d5089569d2 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -647,6 +647,48 @@ impl JsRuntime { scope.perform_microtask_checkpoint(); } + /// Waits for the given value to resolve while polling the event loop. + /// + /// This future resolves when either the value is resolved or the event loop runs to + /// completion. + pub async fn resolve_value( + &mut self, + global: v8::Global, + ) -> Result, AnyError> { + poll_fn(|cx| { + let state = self.poll_event_loop(cx, false); + + let mut scope = self.handle_scope(); + let local = v8::Local::::new(&mut scope, &global); + + if let Ok(promise) = v8::Local::::try_from(local) { + match promise.state() { + v8::PromiseState::Pending => match state { + Poll::Ready(Ok(_)) => { + let msg = "Promise resolution is still pending but the event loop has already resolved."; + Poll::Ready(Err(generic_error(msg))) + }, + Poll::Ready(Err(e)) => Poll::Ready(Err(e)), + Poll::Pending => Poll::Pending, + }, + v8::PromiseState::Fulfilled => { + let value = promise.result(&mut scope); + let value_handle = v8::Global::new(&mut scope, value); + Poll::Ready(Ok(value_handle)) + } + v8::PromiseState::Rejected => { + let exception = promise.result(&mut scope); + Poll::Ready(exception_to_err_result(&mut scope, exception, false)) + } + } + } else { + let value_handle = v8::Global::new(&mut scope, local); + Poll::Ready(Ok(value_handle)) + } + }) + .await + } + /// Runs event loop to completion /// /// This future resolves when: @@ -1628,6 +1670,55 @@ pub mod tests { } } + #[tokio::test] + async fn test_resolve_value() { + let mut runtime = JsRuntime::new(Default::default()); + let value_global = runtime + .execute_script("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.get(scope); + assert_eq!(value.integer_value(scope).unwrap(), 3); + } + + let value_global = runtime + .execute_script( + "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.get(scope); + assert_eq!(value.integer_value(scope).unwrap(), 4); + } + + let value_global = runtime + .execute_script("a.js", "Promise.reject(new Error('fail'))") + .unwrap(); + let err = runtime.resolve_value(value_global).await.unwrap_err(); + assert_eq!( + "Uncaught Error: fail", + err.downcast::().unwrap().message + ); + + let value_global = runtime + .execute_script("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() { let (mut isolate, _dispatch_count) = setup(Mode::Async);