diff --git a/cli/tools/jupyter/mod.rs b/cli/tools/jupyter/mod.rs index 2731f58143..64c8cd7dfc 100644 --- a/cli/tools/jupyter/mod.rs +++ b/cli/tools/jupyter/mod.rs @@ -92,7 +92,7 @@ pub async fn kernel( permissions, vec![ ops::jupyter::deno_jupyter::init_ops(stdio_tx.clone()), - ops::testing::deno_test::init_ops(test_event_sender.clone()), + ops::testing::deno_test::init_ops(test_event_sender), ], // FIXME(nayeemrmn): Test output capturing currently doesn't work. Stdio { @@ -114,7 +114,6 @@ pub async fn kernel( resolver, worker, main_module, - test_event_sender, test_event_receiver, ) .await?; diff --git a/cli/tools/repl/mod.rs b/cli/tools/repl/mod.rs index 698abb6919..80ab2a2d2b 100644 --- a/cli/tools/repl/mod.rs +++ b/cli/tools/repl/mod.rs @@ -172,9 +172,7 @@ pub async fn run(flags: Flags, repl_flags: ReplFlags) -> Result { .create_custom_worker( main_module.clone(), permissions, - vec![crate::ops::testing::deno_test::init_ops( - test_event_sender.clone(), - )], + vec![crate::ops::testing::deno_test::init_ops(test_event_sender)], Default::default(), ) .await?; @@ -186,7 +184,6 @@ pub async fn run(flags: Flags, repl_flags: ReplFlags) -> Result { resolver, worker, main_module, - test_event_sender, test_event_receiver, ) .await?; diff --git a/cli/tools/repl/session.rs b/cli/tools/repl/session.rs index e5f70df15f..840d858228 100644 --- a/cli/tools/repl/session.rs +++ b/cli/tools/repl/session.rs @@ -12,10 +12,10 @@ use crate::tools::test::report_tests; use crate::tools::test::reporters::PrettyTestReporter; use crate::tools::test::reporters::TestReporter; use crate::tools::test::run_tests_for_worker; +use crate::tools::test::send_test_event; use crate::tools::test::worker_has_tests; use crate::tools::test::TestEvent; use crate::tools::test::TestEventReceiver; -use crate::tools::test::TestEventSender; use deno_ast::diagnostics::Diagnostic; use deno_ast::swc::ast as swc_ast; @@ -184,7 +184,6 @@ pub struct ReplSession { referrer: ModuleSpecifier, main_module: ModuleSpecifier, test_reporter_factory: Box Box>, - test_event_sender: TestEventSender, /// This is only optional because it's temporarily taken when evaluating. test_event_receiver: Option, jsx: ReplJsxState, @@ -198,7 +197,6 @@ impl ReplSession { resolver: Arc, mut worker: MainWorker, main_module: ModuleSpecifier, - test_event_sender: TestEventSender, test_event_receiver: TestEventReceiver, ) -> Result { let language_server = ReplLanguageServer::new_initialized().await?; @@ -278,7 +276,6 @@ impl ReplSession { )) }), main_module, - test_event_sender, test_event_receiver: Some(test_event_receiver), jsx: ReplJsxState { factory: "React.createElement".to_string(), @@ -466,10 +463,11 @@ impl ReplSession { ) .await .unwrap(); - self - .test_event_sender - .send(TestEvent::ForceEndReport) - .unwrap(); + send_test_event( + &self.worker.js_runtime.op_state(), + TestEvent::ForceEndReport, + ) + .unwrap(); self.test_event_receiver = Some(report_tests_handle.await.unwrap().1); } diff --git a/cli/tools/test/channel.rs b/cli/tools/test/channel.rs index a024393bb1..aad3f926e7 100644 --- a/cli/tools/test/channel.rs +++ b/cli/tools/test/channel.rs @@ -16,7 +16,6 @@ use std::io::Write; use std::pin::Pin; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; -use std::sync::Arc; use std::task::ready; use std::task::Poll; use std::time::Duration; @@ -310,7 +309,6 @@ impl TestEventSenderFactory { let sender = TestEventSender { id, - ref_count: Default::default(), sender: self.sender.clone(), sync_sender, stdout_writer, @@ -373,26 +371,12 @@ pub struct TestEventWorkerSender { /// are not guaranteed to be sent on drop unless flush is explicitly called. pub struct TestEventSender { pub id: usize, - ref_count: Arc<()>, sender: UnboundedSender<(usize, TestEvent)>, sync_sender: UnboundedSender<(SendMutex, SendMutex)>, stdout_writer: PipeWrite, stderr_writer: PipeWrite, } -impl Clone for TestEventSender { - fn clone(&self) -> Self { - Self { - id: self.id, - ref_count: self.ref_count.clone(), - sender: self.sender.clone(), - sync_sender: self.sync_sender.clone(), - stdout_writer: self.stdout_writer.try_clone().unwrap(), - stderr_writer: self.stderr_writer.try_clone().unwrap(), - } - } -} - impl TestEventSender { pub fn send(&mut self, message: TestEvent) -> Result<(), ChannelClosedError> { // Certain messages require us to ensure that all output has been drained to ensure proper diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index f488271179..390a60762a 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -21,6 +21,7 @@ use crate::util::path::is_script_ext; use crate::util::path::mapped_specifier_for_tsc; use crate::util::path::matches_pattern_or_exact_path; use crate::worker::CliMainWorkerFactory; +use crate::worker::CoverageCollector; use deno_ast::swc::common::comments::CommentKind; use deno_ast::MediaType; @@ -49,6 +50,7 @@ use deno_core::unsync::spawn_blocking; use deno_core::url::Url; use deno_core::v8; use deno_core::ModuleSpecifier; +use deno_core::OpState; use deno_core::PollEventLoopOptions; use deno_runtime::deno_io::Stdio; use deno_runtime::deno_io::StdioPipe; @@ -66,6 +68,7 @@ use rand::SeedableRng; use regex::Regex; use serde::Deserialize; use std::borrow::Cow; +use std::cell::RefCell; use std::collections::BTreeMap; use std::collections::BTreeSet; use std::collections::HashMap; @@ -571,23 +574,81 @@ fn get_test_reporter(options: &TestSpecifiersOptions) -> Box { reporter } +async fn configure_main_worker( + worker_factory: Arc, + specifier: &Url, + permissions: Permissions, + worker_sender: TestEventWorkerSender, + options: &TestSpecifierOptions, +) -> Result<(Option>, MainWorker), anyhow::Error> { + let mut worker = worker_factory + .create_custom_worker( + specifier.clone(), + PermissionsContainer::new(permissions), + vec![ops::testing::deno_test::init_ops(worker_sender.sender)], + Stdio { + stdin: StdioPipe::inherit(), + stdout: StdioPipe::file(worker_sender.stdout), + stderr: StdioPipe::file(worker_sender.stderr), + }, + ) + .await?; + let coverage_collector = worker.maybe_setup_coverage_collector().await?; + if options.trace_leaks { + worker.execute_script_static( + located_script_name!(), + "Deno[Deno.internal].core.setLeakTracingEnabled(true);", + )?; + } + let res = worker.execute_side_module_possibly_with_npm().await; + let mut worker = worker.into_main_worker(); + match res { + Ok(()) => Ok(()), + Err(error) => { + // TODO(mmastrac): It would be nice to avoid having this error pattern repeated + if error.is::() { + send_test_event( + &worker.js_runtime.op_state(), + TestEvent::UncaughtError( + specifier.to_string(), + Box::new(error.downcast::().unwrap()), + ), + )?; + Ok(()) + } else { + Err(error) + } + } + }?; + Ok((coverage_collector, worker)) +} + /// Test a single specifier as documentation containing test programs, an executable test module or /// both. pub async fn test_specifier( worker_factory: Arc, permissions: Permissions, specifier: ModuleSpecifier, - mut worker_sender: TestEventWorkerSender, + worker_sender: TestEventWorkerSender, fail_fast_tracker: FailFastTracker, options: TestSpecifierOptions, ) -> Result<(), AnyError> { - match test_specifier_inner( + if fail_fast_tracker.should_stop() { + return Ok(()); + } + let (coverage_collector, mut worker) = configure_main_worker( worker_factory, + &specifier, permissions, + worker_sender, + &options, + ) + .await?; + + match test_specifier_inner( + &mut worker, + coverage_collector, specifier.clone(), - &mut worker_sender.sender, - StdioPipe::file(worker_sender.stdout), - StdioPipe::file(worker_sender.stderr), fail_fast_tracker, options, ) @@ -595,11 +656,15 @@ pub async fn test_specifier( { Ok(()) => Ok(()), Err(error) => { + // TODO(mmastrac): It would be nice to avoid having this error pattern repeated if error.is::() { - worker_sender.sender.send(TestEvent::UncaughtError( - specifier.to_string(), - Box::new(error.downcast::().unwrap()), - ))?; + send_test_event( + &worker.js_runtime.op_state(), + TestEvent::UncaughtError( + specifier.to_string(), + Box::new(error.downcast::().unwrap()), + ), + )?; Ok(()) } else { Err(error) @@ -612,51 +677,18 @@ pub async fn test_specifier( /// both. #[allow(clippy::too_many_arguments)] async fn test_specifier_inner( - worker_factory: Arc, - permissions: Permissions, + worker: &mut MainWorker, + mut coverage_collector: Option>, specifier: ModuleSpecifier, - sender: &mut TestEventSender, - stdout: StdioPipe, - stderr: StdioPipe, fail_fast_tracker: FailFastTracker, options: TestSpecifierOptions, ) -> Result<(), AnyError> { - if fail_fast_tracker.should_stop() { - return Ok(()); - } - let mut worker = worker_factory - .create_custom_worker( - specifier.clone(), - PermissionsContainer::new(permissions), - vec![ops::testing::deno_test::init_ops(sender.clone())], - Stdio { - stdin: StdioPipe::inherit(), - stdout, - stderr, - }, - ) - .await?; - - let mut coverage_collector = worker.maybe_setup_coverage_collector().await?; - - if options.trace_leaks { - worker.execute_script_static( - located_script_name!(), - "Deno[Deno.internal].core.setLeakTracingEnabled(true);", - )?; - } - - // We execute the main module as a side module so that import.meta.main is not set. - worker.execute_side_module_possibly_with_npm().await?; - - let mut worker = worker.into_main_worker(); - // Ensure that there are no pending exceptions before we start running tests worker.run_up_to_duration(Duration::from_millis(0)).await?; worker.dispatch_load_event()?; - run_tests_for_worker(&mut worker, &specifier, &options, &fail_fast_tracker) + run_tests_for_worker(worker, &specifier, &options, &fail_fast_tracker) .await?; // Ignore `defaultPrevented` of the `beforeunload` event. We don't allow the @@ -665,13 +697,18 @@ async fn test_specifier_inner( worker.dispatch_unload_event()?; // Ensure all output has been flushed - _ = sender.flush(); + _ = worker + .js_runtime + .op_state() + .borrow_mut() + .borrow_mut::() + .flush(); // Ensure the worker has settled so we can catch any remaining unhandled rejections. We don't // want to wait forever here. worker.run_up_to_duration(Duration::from_millis(0)).await?; - if let Some(coverage_collector) = coverage_collector.as_mut() { + if let Some(coverage_collector) = &mut coverage_collector { worker .js_runtime .with_event_loop_future( @@ -708,33 +745,42 @@ pub async fn poll_event_loop(worker: &mut MainWorker) -> Result<(), AnyError> { .await } +pub fn send_test_event( + op_state: &RefCell, + event: TestEvent, +) -> Result<(), AnyError> { + Ok( + op_state + .borrow_mut() + .borrow_mut::() + .send(event)?, + ) +} + pub async fn run_tests_for_worker( worker: &mut MainWorker, specifier: &ModuleSpecifier, options: &TestSpecifierOptions, fail_fast_tracker: &FailFastTracker, ) -> Result<(), AnyError> { - let (TestContainer(tests, test_functions), mut sender) = { - let state_rc = worker.js_runtime.op_state(); - let mut state = state_rc.borrow_mut(); - ( - std::mem::take(&mut *state.borrow_mut::()), - state.borrow::().clone(), - ) - }; + let state_rc = worker.js_runtime.op_state(); + // Take whatever tests have been registered + let TestContainer(tests, test_functions) = + std::mem::take(&mut *state_rc.borrow_mut().borrow_mut::()); + let tests: Arc = tests.into(); - sender.send(TestEvent::Register(tests.clone()))?; + send_test_event(&state_rc, TestEvent::Register(tests.clone()))?; let res = run_tests_for_worker_inner( worker, specifier, tests, test_functions, - &mut sender, options, fail_fast_tracker, ) .await; - _ = sender.send(TestEvent::Completed); + + _ = send_test_event(&state_rc, TestEvent::Completed); res } @@ -743,11 +789,11 @@ async fn run_tests_for_worker_inner( specifier: &ModuleSpecifier, tests: Arc, test_functions: Vec>, - sender: &mut TestEventSender, options: &TestSpecifierOptions, fail_fast_tracker: &FailFastTracker, ) -> Result<(), AnyError> { let unfiltered = tests.len(); + let state_rc = worker.js_runtime.op_state(); // Build the test plan in a single pass let mut tests_to_run = Vec::with_capacity(tests.len()); @@ -775,12 +821,15 @@ async fn run_tests_for_worker_inner( tests_to_run.shuffle(&mut SmallRng::seed_from_u64(seed)); } - sender.send(TestEvent::Plan(TestPlan { - origin: specifier.to_string(), - total: tests_to_run.len(), - filtered_out: unfiltered - tests_to_run.len(), - used_only, - }))?; + send_test_event( + &state_rc, + TestEvent::Plan(TestPlan { + origin: specifier.to_string(), + total: tests_to_run.len(), + filtered_out: unfiltered - tests_to_run.len(), + used_only, + }), + )?; let mut had_uncaught_error = false; let stats = worker.js_runtime.runtime_activity_stats_factory(); @@ -835,14 +884,20 @@ async fn run_tests_for_worker_inner( .try_take::(); if desc.ignore { - sender.send(TestEvent::Result(desc.id, TestResult::Ignored, 0))?; + send_test_event( + &state_rc, + TestEvent::Result(desc.id, TestResult::Ignored, 0), + )?; continue; } if had_uncaught_error { - sender.send(TestEvent::Result(desc.id, TestResult::Cancelled, 0))?; + send_test_event( + &state_rc, + TestEvent::Result(desc.id, TestResult::Cancelled, 0), + )?; continue; } - sender.send(TestEvent::Wait(desc.id))?; + send_test_event(&state_rc, TestEvent::Wait(desc.id))?; // Poll event loop once, to allow all ops that are already resolved, but haven't // responded to settle. @@ -863,12 +918,18 @@ async fn run_tests_for_worker_inner( Ok(r) => r, Err(error) => { if error.is::() { - sender.send(TestEvent::UncaughtError( - specifier.to_string(), - Box::new(error.downcast::().unwrap()), - ))?; + send_test_event( + &state_rc, + TestEvent::UncaughtError( + specifier.to_string(), + Box::new(error.downcast::().unwrap()), + ), + )?; fail_fast_tracker.add_failure(); - sender.send(TestEvent::Result(desc.id, TestResult::Cancelled, 0))?; + send_test_event( + &state_rc, + TestEvent::Result(desc.id, TestResult::Cancelled, 0), + )?; had_uncaught_error = true; continue; } else { @@ -886,7 +947,10 @@ async fn run_tests_for_worker_inner( if matches!(result, TestResult::Failed(_)) { fail_fast_tracker.add_failure(); let elapsed = earlier.elapsed().as_millis(); - sender.send(TestEvent::Result(desc.id, result, elapsed as u64))?; + send_test_event( + &state_rc, + TestEvent::Result(desc.id, result, elapsed as u64), + )?; continue; } @@ -907,17 +971,23 @@ async fn run_tests_for_worker_inner( let failure = TestFailure::Leaked(formatted, trailer_notes); fail_fast_tracker.add_failure(); let elapsed = earlier.elapsed().as_millis(); - sender.send(TestEvent::Result( - desc.id, - TestResult::Failed(failure), - elapsed as u64, - ))?; + send_test_event( + &state_rc, + TestEvent::Result( + desc.id, + TestResult::Failed(failure), + elapsed as u64, + ), + )?; continue; } } let elapsed = earlier.elapsed().as_millis(); - sender.send(TestEvent::Result(desc.id, result, elapsed as u64))?; + send_test_event( + &state_rc, + TestEvent::Result(desc.id, result, elapsed as u64), + )?; } Ok(()) }