diff --git a/cli/js/40_test.js b/cli/js/40_test.js index dc14c79146aaf8..2e448e84734ca6 100644 --- a/cli/js/40_test.js +++ b/cli/js/40_test.js @@ -11,6 +11,7 @@ const { op_test_event_step_result_ignored, op_test_event_step_result_ok, op_test_event_step_wait, + op_test_get_origin, } = core.ops; const { ArrayPrototypeFilter, @@ -188,6 +189,9 @@ function wrapInner(fn) { const registerTestIdRetBuf = new Uint32Array(1); const registerTestIdRetBufU8 = new Uint8Array(registerTestIdRetBuf.buffer); +// As long as we're using one isolate per test, we can cache the origin since it won't change +let cachedOrigin = undefined; + function testInner( nameOrFnOrOptions, optionsOrFn, @@ -279,11 +283,15 @@ function testInner( // Delete this prop in case the user passed it. It's used to detect steps. delete testDesc.parent; + if (cachedOrigin == undefined) { + cachedOrigin = op_test_get_origin(); + } + testDesc.location = core.currentUserCallSite(); testDesc.fn = wrapTest(testDesc); testDesc.name = escapeName(testDesc.name); - const origin = op_register_test( + op_register_test( testDesc.fn, testDesc.name, testDesc.ignore, @@ -296,7 +304,7 @@ function testInner( registerTestIdRetBufU8, ); testDesc.id = registerTestIdRetBuf[0]; - testDesc.origin = origin; + testDesc.origin = cachedOrigin; MapPrototypeSet(testStates, testDesc.id, { context: createTestContext(testDesc), children: [], diff --git a/cli/lsp/testing/execution.rs b/cli/lsp/testing/execution.rs index 5fd1feb5aaf357..809d0245664374 100644 --- a/cli/lsp/testing/execution.rs +++ b/cli/lsp/testing/execution.rs @@ -324,8 +324,11 @@ impl TestRun { while let Some((_, event)) = receiver.recv().await { match event { test::TestEvent::Register(description) => { - reporter.report_register(&description); - tests.write().insert(description.id, description); + for (_, description) in description.into_iter() { + reporter.report_register(description); + // TODO(mmastrac): we shouldn't need to clone here - we can re-use the descriptions + tests.write().insert(description.id, description.clone()); + } } test::TestEvent::Plan(plan) => { summary.total += plan.total; diff --git a/cli/ops/testing.rs b/cli/ops/testing.rs index 8e7a5bb0335b85..eb7cf71ce68d46 100644 --- a/cli/ops/testing.rs +++ b/cli/ops/testing.rs @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use crate::tools::test::TestContainer; use crate::tools::test::TestDescription; use crate::tools::test::TestEvent; use crate::tools::test::TestEventSender; @@ -23,17 +24,13 @@ use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; use uuid::Uuid; -#[derive(Default)] -pub(crate) struct TestContainer( - pub Vec<(TestDescription, v8::Global)>, -); - deno_core::extension!(deno_test, ops = [ op_pledge_test_permissions, op_restore_test_permissions, op_register_test, op_register_test_step, + op_test_get_origin, op_test_event_step_wait, op_test_event_step_result_ok, op_test_event_step_result_ignored, @@ -106,7 +103,6 @@ static NEXT_ID: AtomicUsize = AtomicUsize::new(0); #[allow(clippy::too_many_arguments)] #[op2] -#[string] fn op_register_test( state: &mut OpState, #[global] function: v8::Global, @@ -119,7 +115,7 @@ fn op_register_test( #[smi] line_number: u32, #[smi] column_number: u32, #[buffer] ret_buf: &mut [u8], -) -> Result { +) -> Result<(), AnyError> { if ret_buf.len() != 4 { return Err(type_error(format!( "Invalid ret_buf length: {}", @@ -142,14 +138,16 @@ fn op_register_test( column_number, }, }; - state - .borrow_mut::() - .0 - .push((description.clone(), function)); - let sender = state.borrow_mut::(); - sender.send(TestEvent::Register(description)).ok(); + let container = state.borrow_mut::(); + container.register(description, function); ret_buf.copy_from_slice(&(id as u32).to_le_bytes()); - Ok(origin) + Ok(()) +} + +#[op2] +#[string] +fn op_test_get_origin(state: &mut OpState) -> String { + state.borrow::().to_string() } #[op2(fast)] diff --git a/cli/tools/test/mod.rs b/cli/tools/test/mod.rs index f7a7aed06dc3c1..526f48c214ec88 100644 --- a/cli/tools/test/mod.rs +++ b/cli/tools/test/mod.rs @@ -175,6 +175,51 @@ pub struct TestLocation { pub column_number: u32, } +#[derive(Default)] +pub(crate) struct TestContainer( + TestDescriptions, + Vec>, +); + +impl TestContainer { + pub fn register( + &mut self, + description: TestDescription, + function: v8::Global, + ) { + self.0.tests.insert(description.id, description); + self.1.push(function) + } + + pub fn is_empty(&self) -> bool { + self.1.is_empty() + } +} + +#[derive(Default, Debug)] +pub struct TestDescriptions { + tests: IndexMap, +} + +impl TestDescriptions { + pub fn len(&self) -> usize { + self.tests.len() + } + + pub fn is_empty(&self) -> bool { + self.tests.is_empty() + } +} + +impl<'a> IntoIterator for &'a TestDescriptions { + type Item = <&'a IndexMap as IntoIterator>::Item; + type IntoIter = + <&'a IndexMap as IntoIterator>::IntoIter; + fn into_iter(self) -> Self::IntoIter { + (&self.tests).into_iter() + } +} + #[derive(Debug, Clone, PartialEq, Deserialize, Eq, Hash)] #[serde(rename_all = "camelCase")] pub struct TestDescription { @@ -335,10 +380,9 @@ pub enum TestStdioStream { Stderr, } -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Debug)] pub enum TestEvent { - Register(TestDescription), + Register(Arc), Plan(TestPlan), Wait(usize), Output(TestStdioStream, Vec), @@ -559,7 +603,7 @@ async fn test_specifier_inner( pub fn worker_has_tests(worker: &mut MainWorker) -> bool { let state_rc = worker.js_runtime.op_state(); let state = state_rc.borrow(); - !state.borrow::().0.is_empty() + !state.borrow::().is_empty() } /// Yields to tokio to allow async work to process, and then polls @@ -587,21 +631,23 @@ pub async fn run_tests_for_worker( options: &TestSpecifierOptions, fail_fast_tracker: &FailFastTracker, ) -> Result<(), AnyError> { - let (tests, mut sender) = { + 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::().0), + std::mem::take(&mut *state.borrow_mut::()), state.borrow::().clone(), ) }; + let tests: Arc = tests.into(); + sender.send(TestEvent::Register(tests.clone()))?; let unfiltered = tests.len(); let tests = tests .into_iter() - .filter(|(d, _)| options.filter.includes(&d.name)) + .filter(|(_, d)| options.filter.includes(&d.name)) .collect::>(); let (only, no_only): (Vec<_>, Vec<_>) = - tests.into_iter().partition(|(d, _)| d.only); + tests.into_iter().partition(|(_, d)| d.only); let used_only = !only.is_empty(); let mut tests = if used_only { only } else { no_only }; if let Some(seed) = options.shuffle { @@ -637,7 +683,7 @@ pub async fn run_tests_for_worker( filter = filter.omit_op(op_id_host_recv_ctrl as _); filter = filter.omit_op(op_id_host_recv_message as _); - for (desc, function) in tests { + for ((_, desc), function) in tests.into_iter().zip(test_functions) { if fail_fast_tracker.should_stop() { break; } @@ -1146,8 +1192,11 @@ pub async fn report_tests( while let Some((_, event)) = receiver.recv().await { match event { TestEvent::Register(description) => { - reporter.report_register(&description); - tests.insert(description.id, description); + for (_, description) in description.into_iter() { + reporter.report_register(description); + // TODO(mmastrac): We shouldn't need to clone here -- we can reuse the descriptions everywhere + tests.insert(description.id, description.clone()); + } } TestEvent::Plan(plan) => { if !had_plan { diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js index 21c74aeff13e39..f4727979b2bfee 100644 --- a/runtime/js/99_main.js +++ b/runtime/js/99_main.js @@ -578,6 +578,7 @@ const NOT_IMPORTED_OPS = [ "op_restore_test_permissions", "op_register_test_step", "op_register_test", + "op_test_get_origin", "op_pledge_test_permissions", // TODO(bartlomieju): used in various integration tests - figure out a way diff --git a/tests/unit/ops_test.ts b/tests/unit/ops_test.ts index 4ba7c5ce33246b..6de55f8b667918 100644 --- a/tests/unit/ops_test.ts +++ b/tests/unit/ops_test.ts @@ -1,6 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -const EXPECTED_OP_COUNT = 11; +const EXPECTED_OP_COUNT = 12; Deno.test(function checkExposedOps() { // @ts-ignore TS doesn't allow to index with symbol