From 67b1b0f183332f53773dc90c2dd694d74c884889 Mon Sep 17 00:00:00 2001 From: Nayeem Rahman Date: Thu, 13 Apr 2023 18:43:23 +0100 Subject: [PATCH] refactor(cli): move runTests() and runBenchmarks() to rust (#18563) Stores the test/bench functions in rust op state during registration. The functions are wrapped in JS first so that they return a directly convertible `TestResult`/`BenchResult`. Test steps are still mostly handled in JS since they are pretty much invoked by the user. Allows removing a bunch of infrastructure for communicating between JS and rust. Allows using rust utilities for things like shuffling tests (`Vec::shuffle`). We can progressively move op and resource sanitization to rust as well. Fixes #17122. Fixes #17312. --- cli/js/40_testing.js | 336 ++++++++------------------- cli/lsp/testing/execution.rs | 77 ++---- cli/ops/bench.rs | 54 +++-- cli/ops/testing.rs | 79 +++---- cli/tests/testdata/test/doc_only.out | 1 - cli/tests/testdata/test/shuffle.out | 36 +-- cli/tests/testdata/test/text.out | 1 - cli/tools/bench.rs | 87 ++++++- cli/tools/test.rs | 237 +++++++++++++------ cli/worker.rs | 247 +------------------- 10 files changed, 450 insertions(+), 705 deletions(-) diff --git a/cli/js/40_testing.js b/cli/js/40_testing.js index babbec8c2f..a0dcaf4991 100644 --- a/cli/js/40_testing.js +++ b/cli/js/40_testing.js @@ -2,21 +2,16 @@ const core = globalThis.Deno.core; const ops = core.ops; -const internals = globalThis.__bootstrap.internals; import { setExitHandler } from "ext:runtime/30_os.js"; import { Console } from "ext:deno_console/02_console.js"; import { serializePermissions } from "ext:runtime/10_permissions.js"; import { assert } from "ext:deno_web/00_infra.js"; const primordials = globalThis.__bootstrap.primordials; const { - ArrayFrom, ArrayPrototypeFilter, ArrayPrototypeJoin, - ArrayPrototypeMap, ArrayPrototypePush, ArrayPrototypeShift, - ArrayPrototypeSort, - BigInt, DateNow, Error, FunctionPrototype, @@ -36,6 +31,7 @@ const { } = primordials; const opSanitizerDelayResolveQueue = []; +let hasSetOpSanitizerDelayMacrotask = false; // Even if every resource is closed by the end of a test, there can be a delay // until the pending ops have all finished. This function returns a promise @@ -47,6 +43,10 @@ const opSanitizerDelayResolveQueue = []; // before that, though, in order to give time for worker message ops to finish // (since timeouts of 0 don't queue tasks in the timer queue immediately). function opSanitizerDelay() { + if (!hasSetOpSanitizerDelayMacrotask) { + core.setMacrotaskCallback(handleOpSanitizerDelayMacrotask); + hasSetOpSanitizerDelayMacrotask = true; + } return new Promise((resolve) => { setTimeout(() => { ArrayPrototypePush(opSanitizerDelayResolveQueue, resolve); @@ -415,9 +415,28 @@ function assertExit(fn, isTest) { }; } -function assertTestStepScopes(fn) { +function wrapOuter(fn, desc) { + return async function outerWrapped() { + try { + if (desc.ignore) { + return "ignored"; + } + return await fn(desc) ?? "ok"; + } catch (error) { + return { failed: { jsError: core.destructureError(error) } }; + } finally { + const state = MapPrototypeGet(testStates, desc.id); + for (const childDesc of state.children) { + stepReportResult(childDesc, { failed: "incomplete" }, 0); + } + state.completed = true; + } + }; +} + +function wrapInner(fn) { /** @param desc {TestDescription | TestStepDescription} */ - return async function testStepSanitizer(desc) { + return async function innerWrapped(desc) { function getRunningStepDescs() { const results = []; let childDesc = desc; @@ -458,11 +477,17 @@ function assertTestStepScopes(fn) { }; } await fn(MapPrototypeGet(testStates, desc.id).context); + let failedSteps = 0; for (const childDesc of MapPrototypeGet(testStates, desc.id).children) { - if (!MapPrototypeGet(testStates, childDesc.id).completed) { + const state = MapPrototypeGet(testStates, childDesc.id); + if (!state.completed) { return { failed: "incompleteSteps" }; } + if (state.failed) { + failedSteps++; + } } + return failedSteps == 0 ? null : { failed: { failedSteps } }; }; } @@ -495,7 +520,6 @@ function withPermissions(fn, permissions) { * fn: TestFunction * origin: string, * location: TestLocation, - * filteredOut: boolean, * ignore: boolean, * only: boolean. * sanitizeOps: boolean, @@ -538,7 +562,6 @@ function withPermissions(fn, permissions) { * name: string, * fn: BenchFunction * origin: string, - * filteredOut: boolean, * ignore: boolean, * only: boolean. * sanitizeExit: boolean, @@ -546,14 +569,8 @@ function withPermissions(fn, permissions) { * }} BenchDescription */ -/** @type {TestDescription[]} */ -const testDescs = []; /** @type {Map} */ const testStates = new Map(); -/** @type {BenchDescription[]} */ -const benchDescs = []; -let isTestSubcommand = false; -let isBenchSubcommand = false; // Main test function provided by Deno. function test( @@ -561,7 +578,7 @@ function test( optionsOrFn, maybeFn, ) { - if (!isTestSubcommand) { + if (typeof ops.op_register_test != "function") { return; } @@ -647,19 +664,17 @@ function test( // Delete this prop in case the user passed it. It's used to detect steps. delete testDesc.parent; - testDesc.origin = getTestOrigin(); const jsError = core.destructureError(new Error()); testDesc.location = { fileName: jsError.frames[1].fileName, lineNumber: jsError.frames[1].lineNumber, columnNumber: jsError.frames[1].columnNumber, }; + testDesc.fn = wrapTest(testDesc); - const { id, filteredOut } = ops.op_register_test(testDesc); + const { id, origin } = ops.op_register_test(testDesc); testDesc.id = id; - testDesc.filteredOut = filteredOut; - - ArrayPrototypePush(testDescs, testDesc); + testDesc.origin = origin; MapPrototypeSet(testStates, testDesc.id, { context: createTestContext(testDesc), children: [], @@ -673,7 +688,7 @@ function bench( optionsOrFn, maybeFn, ) { - if (!isBenchSubcommand) { + if (typeof ops.op_register_bench != "function") { return; } @@ -756,36 +771,13 @@ function bench( benchDesc = { ...defaults, ...nameOrFnOrOptions, fn, name }; } - benchDesc.origin = getBenchOrigin(); const AsyncFunction = (async () => {}).constructor; benchDesc.async = AsyncFunction === benchDesc.fn.constructor; + benchDesc.fn = wrapBenchmark(benchDesc); - const { id, filteredOut } = ops.op_register_bench(benchDesc); + const { id, origin } = ops.op_register_bench(benchDesc); benchDesc.id = id; - benchDesc.filteredOut = filteredOut; - - ArrayPrototypePush(benchDescs, benchDesc); -} - -async function runTest(desc) { - if (desc.ignore) { - return "ignored"; - } - let testFn = wrapTestFnWithSanitizers(desc.fn, desc); - if (!("parent" in desc) && desc.permissions) { - testFn = withPermissions( - testFn, - desc.permissions, - ); - } - try { - const result = await testFn(desc); - if (result) return result; - const failedSteps = failedChildStepsCount(desc); - return failedSteps === 0 ? "ok" : { failed: { failedSteps } }; - } catch (error) { - return { failed: { jsError: core.destructureError(error) } }; - } + benchDesc.origin = origin; } function compareMeasurements(a, b) { @@ -808,8 +800,7 @@ function benchStats(n, highPrecision, avg, min, max, all) { }; } -async function benchMeasure(timeBudget, desc) { - const fn = desc.fn; +async function benchMeasure(timeBudget, fn, async) { let n = 0; let avg = 0; let wavg = 0; @@ -823,7 +814,7 @@ async function benchMeasure(timeBudget, desc) { let iterations = 20; let budget = 10 * 1e6; - if (!desc.async) { + if (!async) { while (budget > 0 || iterations-- > 0) { const t1 = benchNow(); @@ -854,7 +845,7 @@ async function benchMeasure(timeBudget, desc) { let iterations = 10; let budget = timeBudget * 1e6; - if (!desc.async) { + if (!async) { while (budget > 0 || iterations-- > 0) { const t1 = benchNow(); @@ -887,7 +878,7 @@ async function benchMeasure(timeBudget, desc) { let iterations = 10; let budget = timeBudget * 1e6; - if (!desc.async) { + if (!async) { while (budget > 0 || iterations-- > 0) { const t1 = benchNow(); for (let c = 0; c < lowPrecisionThresholdInNs; c++) fn(); @@ -920,173 +911,49 @@ async function benchMeasure(timeBudget, desc) { return benchStats(n, wavg > lowPrecisionThresholdInNs, avg, min, max, all); } -async function runBench(desc) { - let token = null; +/** Wrap a user benchmark function in one which returns a structured result. */ +function wrapBenchmark(desc) { + const fn = desc.fn; + return async function outerWrapped() { + let token = null; + const originalConsole = globalThis.console; - try { - if (desc.permissions) { - token = pledgePermissions(desc.permissions); - } - - if (desc.sanitizeExit) { - setExitHandler((exitCode) => { - assert( - false, - `Bench attempted to exit with exit code: ${exitCode}`, - ); + try { + globalThis.console = new Console((s) => { + ops.op_dispatch_bench_event({ output: s }); }); + + if (desc.permissions) { + token = pledgePermissions(desc.permissions); + } + + if (desc.sanitizeExit) { + setExitHandler((exitCode) => { + assert( + false, + `Bench attempted to exit with exit code: ${exitCode}`, + ); + }); + } + + const benchTimeInMs = 500; + const stats = await benchMeasure(benchTimeInMs, fn, desc.async); + + return { ok: stats }; + } catch (error) { + return { failed: core.destructureError(error) }; + } finally { + globalThis.console = originalConsole; + if (bench.sanitizeExit) setExitHandler(null); + if (token !== null) restorePermissions(token); } - - const benchTimeInMs = 500; - const stats = await benchMeasure(benchTimeInMs, desc); - - return { ok: stats }; - } catch (error) { - return { failed: core.destructureError(error) }; - } finally { - if (bench.sanitizeExit) setExitHandler(null); - if (token !== null) restorePermissions(token); - } -} - -let origin = null; - -function getTestOrigin() { - if (origin == null) { - origin = ops.op_get_test_origin(); - } - return origin; -} - -function getBenchOrigin() { - if (origin == null) { - origin = ops.op_get_bench_origin(); - } - return origin; + }; } function benchNow() { return ops.op_bench_now(); } -function enableTest() { - isTestSubcommand = true; -} - -function enableBench() { - isBenchSubcommand = true; -} - -async function runTests({ - shuffle = null, -} = {}) { - core.setMacrotaskCallback(handleOpSanitizerDelayMacrotask); - - const origin = getTestOrigin(); - const only = ArrayPrototypeFilter(testDescs, (test) => test.only); - const filtered = ArrayPrototypeFilter( - only.length > 0 ? only : testDescs, - (desc) => !desc.filteredOut, - ); - - ops.op_dispatch_test_event({ - plan: { - origin, - total: filtered.length, - filteredOut: testDescs.length - filtered.length, - usedOnly: only.length > 0, - }, - }); - - if (shuffle !== null) { - // http://en.wikipedia.org/wiki/Linear_congruential_generator - // Use BigInt for everything because the random seed is u64. - const nextInt = function (state) { - const m = 0x80000000n; - const a = 1103515245n; - const c = 12345n; - - return function (max) { - return state = ((a * state + c) % m) % BigInt(max); - }; - }(BigInt(shuffle)); - - for (let i = filtered.length - 1; i > 0; i--) { - const j = nextInt(i); - [filtered[i], filtered[j]] = [filtered[j], filtered[i]]; - } - } - - for (const desc of filtered) { - if (ops.op_tests_should_stop()) { - break; - } - ops.op_dispatch_test_event({ wait: desc.id }); - const earlier = DateNow(); - const result = await runTest(desc); - const elapsed = DateNow() - earlier; - const state = MapPrototypeGet(testStates, desc.id); - state.completed = true; - for (const childDesc of state.children) { - stepReportResult(childDesc, { failed: "incomplete" }, 0); - } - ops.op_dispatch_test_event({ - result: [desc.id, result, elapsed], - }); - } -} - -async function runBenchmarks() { - core.setMacrotaskCallback(handleOpSanitizerDelayMacrotask); - - const origin = getBenchOrigin(); - const originalConsole = globalThis.console; - - globalThis.console = new Console((s) => { - ops.op_dispatch_bench_event({ output: s }); - }); - - const only = ArrayPrototypeFilter(benchDescs, (bench) => bench.only); - const filtered = ArrayPrototypeFilter( - only.length > 0 ? only : benchDescs, - (desc) => !desc.filteredOut && !desc.ignore, - ); - - let groups = new Set(); - // make sure ungrouped benchmarks are placed above grouped - groups.add(undefined); - - for (const desc of filtered) { - desc.group ||= undefined; - groups.add(desc.group); - } - - groups = ArrayFrom(groups); - ArrayPrototypeSort( - filtered, - (a, b) => groups.indexOf(a.group) - groups.indexOf(b.group), - ); - - ops.op_dispatch_bench_event({ - plan: { - origin, - total: filtered.length, - usedOnly: only.length > 0, - names: ArrayPrototypeMap(filtered, (desc) => desc.name), - }, - }); - - for (const desc of filtered) { - desc.baseline = !!desc.baseline; - ops.op_dispatch_bench_event({ wait: desc.id }); - ops.op_dispatch_bench_event({ - result: [desc.id, await runBench(desc)], - }); - } - - globalThis.console = originalConsole; -} - function getFullName(desc) { if ("parent" in desc) { return `${getFullName(desc.parent)} ... ${desc.name}`; @@ -1108,13 +975,6 @@ function stepReportResult(desc, result, elapsed) { }); } -function failedChildStepsCount(desc) { - return ArrayPrototypeFilter( - MapPrototypeGet(testStates, desc.id).children, - (d) => MapPrototypeGet(testStates, d.id).failed, - ).length; -} - /** @param desc {TestDescription | TestStepDescription} */ function createTestContext(desc) { let parent; @@ -1191,7 +1051,6 @@ function createTestContext(desc) { stepDesc.sanitizeOps ??= desc.sanitizeOps; stepDesc.sanitizeResources ??= desc.sanitizeResources; stepDesc.sanitizeExit ??= desc.sanitizeExit; - stepDesc.origin = getTestOrigin(); const jsError = core.destructureError(new Error()); stepDesc.location = { fileName: jsError.frames[1].fileName, @@ -1202,8 +1061,10 @@ function createTestContext(desc) { stepDesc.parent = desc; stepDesc.rootId = rootId; stepDesc.rootName = rootName; - const { id } = ops.op_register_test_step(stepDesc); + stepDesc.fn = wrapTest(stepDesc); + const { id, origin } = ops.op_register_test_step(stepDesc); stepDesc.id = id; + stepDesc.origin = origin; const state = { context: createTestContext(stepDesc), children: [], @@ -1218,10 +1079,9 @@ function createTestContext(desc) { ops.op_dispatch_test_event({ stepWait: stepDesc.id }); const earlier = DateNow(); - const result = await runTest(stepDesc); + const result = await stepDesc.fn(stepDesc); const elapsed = DateNow() - earlier; state.failed = !!result.failed; - state.completed = true; stepReportResult(stepDesc, result, elapsed); return result == "ok"; }, @@ -1229,37 +1089,29 @@ function createTestContext(desc) { } /** + * Wrap a user test function in one which returns a structured result. * @template T {Function} * @param testFn {T} - * @param opts {{ - * sanitizeOps: boolean, - * sanitizeResources: boolean, - * sanitizeExit: boolean, - * }} + * @param desc {TestDescription | TestStepDescription} * @returns {T} */ -function wrapTestFnWithSanitizers(testFn, opts) { - testFn = assertTestStepScopes(testFn); - - if (opts.sanitizeOps) { +function wrapTest(desc) { + let testFn = wrapInner(desc.fn); + if (desc.sanitizeOps) { testFn = assertOps(testFn); } - if (opts.sanitizeResources) { + if (desc.sanitizeResources) { testFn = assertResources(testFn); } - if (opts.sanitizeExit) { + if (desc.sanitizeExit) { testFn = assertExit(testFn, true); } - return testFn; + if (!("parent" in desc) && desc.permissions) { + testFn = withPermissions(testFn, desc.permissions); + } + return wrapOuter(testFn, desc); } -internals.testing = { - runTests, - runBenchmarks, - enableTest, - enableBench, -}; - import { denoNs } from "ext:runtime/90_deno_ns.js"; denoNs.bench = bench; denoNs.test = test; diff --git a/cli/lsp/testing/execution.rs b/cli/lsp/testing/execution.rs index 466c0d9423..020dd5c08a 100644 --- a/cli/lsp/testing/execution.rs +++ b/cli/lsp/testing/execution.rs @@ -10,13 +10,11 @@ use crate::lsp::client::Client; use crate::lsp::client::TestingNotification; use crate::lsp::config; use crate::lsp::logging::lsp_log; -use crate::ops; use crate::proc_state; use crate::tools::test; use crate::tools::test::FailFastTracker; use crate::tools::test::TestEventSender; use crate::util::checksum; -use crate::worker::create_main_worker_for_test_or_bench; use deno_core::anyhow::anyhow; use deno_core::error::AnyError; @@ -27,10 +25,7 @@ use deno_core::futures::StreamExt; use deno_core::parking_lot::Mutex; use deno_core::parking_lot::RwLock; use deno_core::ModuleSpecifier; -use deno_runtime::deno_io::Stdio; -use deno_runtime::deno_io::StdioPipe; use deno_runtime::permissions::Permissions; -use deno_runtime::permissions::PermissionsContainer; use deno_runtime::tokio_util::run_local; use indexmap::IndexMap; use std::collections::HashMap; @@ -147,42 +142,6 @@ impl LspTestFilter { } } -#[allow(clippy::too_many_arguments)] -async fn test_specifier( - ps: proc_state::ProcState, - permissions: Permissions, - specifier: ModuleSpecifier, - mode: test::TestMode, - sender: TestEventSender, - fail_fast_tracker: FailFastTracker, - token: CancellationToken, - filter: test::TestFilter, -) -> Result<(), AnyError> { - if !token.is_cancelled() { - let stdout = StdioPipe::File(sender.stdout()); - let stderr = StdioPipe::File(sender.stderr()); - let mut worker = create_main_worker_for_test_or_bench( - &ps, - specifier.clone(), - PermissionsContainer::new(permissions), - vec![ops::testing::deno_test::init_ops( - sender, - fail_fast_tracker, - filter, - )], - Stdio { - stdin: StdioPipe::Inherit, - stdout, - stderr, - }, - ) - .await?; - worker.run_lsp_test_specifier(mode).await?; - } - - Ok(()) -} - #[derive(Debug, Clone)] pub struct TestRun { id: u32, @@ -300,7 +259,6 @@ impl TestRun { Arc::new(RwLock::new(IndexMap::new())); let mut test_steps = IndexMap::new(); - let tests_ = tests.clone(); let join_handles = queue.into_iter().map(move |specifier| { let specifier = specifier.clone(); let ps = ps.clone(); @@ -321,38 +279,30 @@ impl TestRun { .unwrap_or_default(), }; let token = self.token.clone(); - let tests = tests_.clone(); tokio::task::spawn_blocking(move || { if fail_fast_tracker.should_stop() { return Ok(()); } let origin = specifier.to_string(); - let file_result = run_local(test_specifier( - ps, - permissions, - specifier, - test::TestMode::Executable, - sender.clone(), - fail_fast_tracker, - token, - filter, - )); + let file_result = if token.is_cancelled() { + Ok(()) + } else { + run_local(test::test_specifier( + &ps, + permissions, + specifier, + sender.clone(), + fail_fast_tracker, + filter, + )) + }; if let Err(error) = file_result { if error.is::() { sender.send(test::TestEvent::UncaughtError( - origin.clone(), + origin, Box::new(error.downcast::().unwrap()), ))?; - for desc in tests.read().values() { - if desc.origin == origin { - sender.send(test::TestEvent::Result( - desc.id, - test::TestResult::Cancelled, - 0, - ))? - } - } } else { return Err(error); } @@ -489,6 +439,7 @@ impl TestRun { .iter() .map(|s| s.as_str()), ); + args.push("--trace-ops"); if self.workspace_settings.unstable && !args.contains(&"--unstable") { args.push("--unstable"); } diff --git a/cli/ops/bench.rs b/cli/ops/bench.rs index 86498cd7cd..da0f3d959a 100644 --- a/cli/ops/bench.rs +++ b/cli/ops/bench.rs @@ -7,6 +7,8 @@ use std::time; use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::op; +use deno_core::serde_v8; +use deno_core::v8; use deno_core::ModuleSpecifier; use deno_core::OpState; use deno_runtime::permissions::create_child_permissions; @@ -19,24 +21,26 @@ use uuid::Uuid; use crate::tools::bench::BenchDescription; use crate::tools::bench::BenchEvent; -use crate::tools::test::TestFilter; + +#[derive(Default)] +pub(crate) struct BenchContainer( + pub Vec<(BenchDescription, v8::Global)>, +); deno_core::extension!(deno_bench, ops = [ op_pledge_test_permissions, op_restore_test_permissions, - op_get_bench_origin, op_register_bench, op_dispatch_bench_event, op_bench_now, ], options = { sender: UnboundedSender, - filter: TestFilter, }, state = |state, options| { state.put(options.sender); - state.put(options.filter); + state.put(BenchContainer::default()); }, customizer = |ext: &mut deno_core::ExtensionBuilder| { ext.force_op_registration(); @@ -90,51 +94,61 @@ pub fn op_restore_test_permissions( } } -#[op] -fn op_get_bench_origin(state: &mut OpState) -> String { - state.borrow::().to_string() -} - -#[derive(Debug, Deserialize)] +#[derive(Deserialize)] #[serde(rename_all = "camelCase")] -struct BenchInfo { +struct BenchInfo<'s> { + #[serde(rename = "fn")] + function: serde_v8::Value<'s>, name: String, - origin: String, baseline: bool, group: Option, + ignore: bool, + only: bool, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] struct BenchRegisterResult { id: usize, - filtered_out: bool, + origin: String, } static NEXT_ID: AtomicUsize = AtomicUsize::new(0); -#[op] -fn op_register_bench( +#[op(v8)] +fn op_register_bench<'a>( + scope: &mut v8::HandleScope<'a>, state: &mut OpState, - info: BenchInfo, + info: BenchInfo<'a>, ) -> Result { let id = NEXT_ID.fetch_add(1, Ordering::SeqCst); - let filter = state.borrow::().clone(); - let filtered_out = !filter.includes(&info.name); + let origin = state.borrow::().to_string(); let description = BenchDescription { id, name: info.name, - origin: info.origin, + origin: origin.clone(), baseline: info.baseline, group: info.group, + ignore: info.ignore, + only: info.only, }; + let function: v8::Local = info.function.v8_value.try_into()?; + let function = v8::Global::new(scope, function); + state + .borrow_mut::() + .0 + .push((description.clone(), function)); let sender = state.borrow::>().clone(); sender.send(BenchEvent::Register(description)).ok(); - Ok(BenchRegisterResult { id, filtered_out }) + Ok(BenchRegisterResult { id, origin }) } #[op] fn op_dispatch_bench_event(state: &mut OpState, event: BenchEvent) { + assert!( + matches!(event, BenchEvent::Output(_)), + "Only output events are expected from JS." + ); let sender = state.borrow::>().clone(); sender.send(event).ok(); } diff --git a/cli/ops/testing.rs b/cli/ops/testing.rs index 8b5c95feae..e36d7e6114 100644 --- a/cli/ops/testing.rs +++ b/cli/ops/testing.rs @@ -1,17 +1,16 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -use crate::tools::test::FailFastTracker; use crate::tools::test::TestDescription; use crate::tools::test::TestEvent; use crate::tools::test::TestEventSender; -use crate::tools::test::TestFilter; use crate::tools::test::TestLocation; -use crate::tools::test::TestResult; use crate::tools::test::TestStepDescription; use deno_core::error::generic_error; use deno_core::error::AnyError; use deno_core::op; +use deno_core::serde_v8; +use deno_core::v8; use deno_core::ModuleSpecifier; use deno_core::OpState; use deno_runtime::permissions::create_child_permissions; @@ -24,25 +23,25 @@ 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_get_test_origin, op_register_test, op_register_test_step, op_dispatch_test_event, - op_tests_should_stop, ], options = { sender: TestEventSender, - fail_fast_tracker: FailFastTracker, - filter: TestFilter, }, state = |state, options| { state.put(options.sender); - state.put(options.fail_fast_tracker); - state.put(options.filter); + state.put(TestContainer::default()); }, customizer = |ext: &mut deno_core::ExtensionBuilder| { ext.force_op_registration(); @@ -95,16 +94,14 @@ pub fn op_restore_test_permissions( } } -#[op] -fn op_get_test_origin(state: &mut OpState) -> Result { - Ok(state.borrow::().to_string()) -} - -#[derive(Debug, Deserialize)] +#[derive(Deserialize)] #[serde(rename_all = "camelCase")] -struct TestInfo { +struct TestInfo<'s> { + #[serde(rename = "fn")] + function: serde_v8::Value<'s>, name: String, - origin: String, + ignore: bool, + only: bool, location: TestLocation, } @@ -112,28 +109,36 @@ struct TestInfo { #[serde(rename_all = "camelCase")] struct TestRegisterResult { id: usize, - filtered_out: bool, + origin: String, } static NEXT_ID: AtomicUsize = AtomicUsize::new(0); -#[op] -fn op_register_test( +#[op(v8)] +fn op_register_test<'a>( + scope: &mut v8::HandleScope<'a>, state: &mut OpState, - info: TestInfo, + info: TestInfo<'a>, ) -> Result { let id = NEXT_ID.fetch_add(1, Ordering::SeqCst); - let filter = state.borrow::().clone(); - let filtered_out = !filter.includes(&info.name); + let origin = state.borrow::().to_string(); let description = TestDescription { id, name: info.name, - origin: info.origin, + ignore: info.ignore, + only: info.only, + origin: origin.clone(), location: info.location, }; + let function: v8::Local = info.function.v8_value.try_into()?; + let function = v8::Global::new(scope, function); + state + .borrow_mut::() + .0 + .push((description.clone(), function)); let mut sender = state.borrow::().clone(); sender.send(TestEvent::Register(description)).ok(); - Ok(TestRegisterResult { id, filtered_out }) + Ok(TestRegisterResult { id, origin }) } fn deserialize_parent<'de, D>(deserializer: D) -> Result @@ -151,7 +156,6 @@ where #[serde(rename_all = "camelCase")] struct TestStepInfo { name: String, - origin: String, location: TestLocation, level: usize, #[serde(rename = "parent")] @@ -167,10 +171,11 @@ fn op_register_test_step( info: TestStepInfo, ) -> Result { let id = NEXT_ID.fetch_add(1, Ordering::SeqCst); + let origin = state.borrow::().to_string(); let description = TestStepDescription { id, name: info.name, - origin: info.origin, + origin: origin.clone(), location: info.location, level: info.level, parent_id: info.parent_id, @@ -179,10 +184,7 @@ fn op_register_test_step( }; let mut sender = state.borrow::().clone(); sender.send(TestEvent::StepRegister(description)).ok(); - Ok(TestRegisterResult { - id, - filtered_out: false, - }) + Ok(TestRegisterResult { id, origin }) } #[op] @@ -190,18 +192,11 @@ fn op_dispatch_test_event( state: &mut OpState, event: TestEvent, ) -> Result<(), AnyError> { - if matches!( - event, - TestEvent::Result(_, TestResult::Cancelled | TestResult::Failed(_), _) - ) { - state.borrow::().add_failure(); - } + assert!( + matches!(event, TestEvent::StepWait(_) | TestEvent::StepResult(..)), + "Only step wait/result events are expected from JS." + ); let mut sender = state.borrow::().clone(); sender.send(event).ok(); Ok(()) } - -#[op] -fn op_tests_should_stop(state: &mut OpState) -> bool { - state.borrow::().should_stop() -} diff --git a/cli/tests/testdata/test/doc_only.out b/cli/tests/testdata/test/doc_only.out index a6cb89df06..2b8b6dc735 100644 --- a/cli/tests/testdata/test/doc_only.out +++ b/cli/tests/testdata/test/doc_only.out @@ -1,5 +1,4 @@ Check [WILDCARD]/test/doc_only/mod.ts$2-5.ts -running 0 tests from ./test/doc_only/mod.ts ok | 0 passed | 0 failed ([WILDCARD]) diff --git a/cli/tests/testdata/test/shuffle.out b/cli/tests/testdata/test/shuffle.out index 28bd97d5c7..fdc2ca9edc 100644 --- a/cli/tests/testdata/test/shuffle.out +++ b/cli/tests/testdata/test/shuffle.out @@ -2,38 +2,38 @@ Check [WILDCARD]/test/shuffle/bar_test.ts Check [WILDCARD]/test/shuffle/baz_test.ts Check [WILDCARD]/test/shuffle/foo_test.ts running 10 tests from ./test/shuffle/foo_test.ts -test 2 ... ok ([WILDCARD]) test 3 ... ok ([WILDCARD]) -test 6 ... ok ([WILDCARD]) -test 9 ... ok ([WILDCARD]) -test 8 ... ok ([WILDCARD]) +test 2 ... ok ([WILDCARD]) test 7 ... ok ([WILDCARD]) test 5 ... ok ([WILDCARD]) -test 4 ... ok ([WILDCARD]) -test 1 ... ok ([WILDCARD]) +test 8 ... ok ([WILDCARD]) test 0 ... ok ([WILDCARD]) +test 9 ... ok ([WILDCARD]) +test 4 ... ok ([WILDCARD]) +test 6 ... ok ([WILDCARD]) +test 1 ... ok ([WILDCARD]) running 10 tests from ./test/shuffle/baz_test.ts -test 2 ... ok ([WILDCARD]) test 3 ... ok ([WILDCARD]) -test 6 ... ok ([WILDCARD]) -test 9 ... ok ([WILDCARD]) -test 8 ... ok ([WILDCARD]) +test 2 ... ok ([WILDCARD]) test 7 ... ok ([WILDCARD]) test 5 ... ok ([WILDCARD]) -test 4 ... ok ([WILDCARD]) -test 1 ... ok ([WILDCARD]) +test 8 ... ok ([WILDCARD]) test 0 ... ok ([WILDCARD]) +test 9 ... ok ([WILDCARD]) +test 4 ... ok ([WILDCARD]) +test 6 ... ok ([WILDCARD]) +test 1 ... ok ([WILDCARD]) running 10 tests from ./test/shuffle/bar_test.ts -test 2 ... ok ([WILDCARD]) test 3 ... ok ([WILDCARD]) -test 6 ... ok ([WILDCARD]) -test 9 ... ok ([WILDCARD]) -test 8 ... ok ([WILDCARD]) +test 2 ... ok ([WILDCARD]) test 7 ... ok ([WILDCARD]) test 5 ... ok ([WILDCARD]) -test 4 ... ok ([WILDCARD]) -test 1 ... ok ([WILDCARD]) +test 8 ... ok ([WILDCARD]) test 0 ... ok ([WILDCARD]) +test 9 ... ok ([WILDCARD]) +test 4 ... ok ([WILDCARD]) +test 6 ... ok ([WILDCARD]) +test 1 ... ok ([WILDCARD]) ok | 30 passed | 0 failed ([WILDCARD]) diff --git a/cli/tests/testdata/test/text.out b/cli/tests/testdata/test/text.out index 354dc24b50..f1b7f7d01d 100644 --- a/cli/tests/testdata/test/text.out +++ b/cli/tests/testdata/test/text.out @@ -1,4 +1,3 @@ -running 0 tests from ./test/text.md ok | 0 passed | 0 failed ([WILDCARD]) diff --git a/cli/tools/bench.rs b/cli/tools/bench.rs index c864c0221f..2fba29efe0 100644 --- a/cli/tools/bench.rs +++ b/cli/tools/bench.rs @@ -15,7 +15,7 @@ use crate::util::file_watcher::ResolutionResult; use crate::util::fs::collect_specifiers; use crate::util::path::is_supported_ext; use crate::version::get_user_agent; -use crate::worker::create_main_worker_for_test_or_bench; +use crate::worker::create_custom_worker; use deno_core::error::generic_error; use deno_core::error::AnyError; @@ -24,11 +24,15 @@ use deno_core::futures::future; use deno_core::futures::stream; use deno_core::futures::FutureExt; use deno_core::futures::StreamExt; +use deno_core::located_script_name; +use deno_core::serde_v8; +use deno_core::v8; use deno_core::ModuleSpecifier; use deno_runtime::permissions::Permissions; use deno_runtime::permissions::PermissionsContainer; use deno_runtime::tokio_util::run_local; use indexmap::IndexMap; +use indexmap::IndexSet; use log::Level; use serde::Deserialize; use serde::Serialize; @@ -87,6 +91,8 @@ pub struct BenchDescription { pub origin: String, pub baseline: bool, pub group: Option, + pub ignore: bool, + pub only: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -433,20 +439,80 @@ async fn bench_specifier( ps: ProcState, permissions: Permissions, specifier: ModuleSpecifier, - channel: UnboundedSender, - options: BenchSpecifierOptions, + sender: UnboundedSender, + filter: TestFilter, ) -> Result<(), AnyError> { - let filter = options.filter; - let mut worker = create_main_worker_for_test_or_bench( + let mut worker = create_custom_worker( &ps, - specifier, + specifier.clone(), PermissionsContainer::new(permissions), - vec![ops::bench::deno_bench::init_ops(channel, filter)], + vec![ops::bench::deno_bench::init_ops(sender.clone())], Default::default(), ) .await?; - worker.run_bench_specifier().await + // 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(); + worker.dispatch_load_event(located_script_name!())?; + + let benchmarks = { + let state_rc = worker.js_runtime.op_state(); + let mut state = state_rc.borrow_mut(); + std::mem::take(&mut state.borrow_mut::().0) + }; + let (only, no_only): (Vec<_>, Vec<_>) = + benchmarks.into_iter().partition(|(d, _)| d.only); + let used_only = !only.is_empty(); + let benchmarks = if used_only { only } else { no_only }; + let mut benchmarks = benchmarks + .into_iter() + .filter(|(d, _)| filter.includes(&d.name) && !d.ignore) + .collect::>(); + let mut groups = IndexSet::>::new(); + // make sure ungrouped benchmarks are placed above grouped + groups.insert(None); + for (desc, _) in &benchmarks { + groups.insert(desc.group.clone()); + } + benchmarks.sort_by(|(d1, _), (d2, _)| { + groups + .get_index_of(&d1.group) + .unwrap() + .partial_cmp(&groups.get_index_of(&d2.group).unwrap()) + .unwrap() + }); + sender.send(BenchEvent::Plan(BenchPlan { + origin: specifier.to_string(), + total: benchmarks.len(), + used_only, + names: benchmarks.iter().map(|(d, _)| d.name.clone()).collect(), + }))?; + for (desc, function) in benchmarks { + sender.send(BenchEvent::Wait(desc.id))?; + let promise = { + let scope = &mut worker.js_runtime.handle_scope(); + let cb = function.open(scope); + let this = v8::undefined(scope).into(); + let promise = cb.call(scope, this, &[]).unwrap(); + v8::Global::new(scope, promise) + }; + let result = worker.js_runtime.resolve_value(promise).await?; + let scope = &mut worker.js_runtime.handle_scope(); + let result = v8::Local::new(scope, result); + let result = serde_v8::from_v8::(scope, result)?; + sender.send(BenchEvent::Result(desc.id, result))?; + } + + loop { + if !worker.dispatch_beforeunload_event(located_script_name!())? { + break; + } + worker.run_event_loop(false).await?; + } + worker.dispatch_unload_event(located_script_name!())?; + Ok(()) } /// Test a collection of specifiers with test modes concurrently. @@ -468,10 +534,9 @@ async fn bench_specifiers( let specifier = specifier; let sender = sender.clone(); let options = option_for_handles.clone(); - tokio::task::spawn_blocking(move || { - let future = bench_specifier(ps, permissions, specifier, sender, options); - + let future = + bench_specifier(ps, permissions, specifier, sender, options.filter); run_local(future) }) }); diff --git a/cli/tools/test.rs b/cli/tools/test.rs index 7d6a6baa4b..f90a68561f 100644 --- a/cli/tools/test.rs +++ b/cli/tools/test.rs @@ -17,7 +17,7 @@ use crate::util::fs::collect_specifiers; use crate::util::path::get_extension; use crate::util::path::is_supported_ext; use crate::util::path::mapped_specifier_for_tsc; -use crate::worker::create_main_worker_for_test_or_bench; +use crate::worker::create_custom_worker; use deno_ast::swc::common::comments::CommentKind; use deno_ast::MediaType; @@ -29,8 +29,11 @@ use deno_core::futures::future; use deno_core::futures::stream; use deno_core::futures::FutureExt; use deno_core::futures::StreamExt; +use deno_core::located_script_name; use deno_core::parking_lot::Mutex; +use deno_core::serde_v8; use deno_core::url::Url; +use deno_core::v8; use deno_core::ModuleSpecifier; use deno_runtime::deno_io::Stdio; use deno_runtime::deno_io::StdioPipe; @@ -63,6 +66,7 @@ use std::sync::atomic::Ordering; use std::sync::Arc; use std::time::Duration; use std::time::Instant; +use std::time::SystemTime; use tokio::signal; use tokio::sync::mpsc::unbounded_channel; use tokio::sync::mpsc::UnboundedSender; @@ -144,6 +148,8 @@ pub struct TestLocation { pub struct TestDescription { pub id: usize, pub name: String, + pub ignore: bool, + pub only: bool, pub origin: String, pub location: TestLocation, } @@ -900,26 +906,24 @@ pub fn format_test_error(js_error: &JsError) -> String { /// Test a single specifier as documentation containing test programs, an executable test module or /// both. -async fn test_specifier( +pub async fn test_specifier( ps: &ProcState, permissions: Permissions, specifier: ModuleSpecifier, - mode: TestMode, - sender: TestEventSender, + mut sender: TestEventSender, fail_fast_tracker: FailFastTracker, - options: TestSpecifierOptions, + filter: TestFilter, ) -> Result<(), AnyError> { + if fail_fast_tracker.should_stop() { + return Ok(()); + } let stdout = StdioPipe::File(sender.stdout()); let stderr = StdioPipe::File(sender.stderr()); - let mut worker = create_main_worker_for_test_or_bench( + let mut worker = create_custom_worker( ps, - specifier, + specifier.clone(), PermissionsContainer::new(permissions), - vec![ops::testing::deno_test::init_ops( - sender, - fail_fast_tracker, - options.filter, - )], + vec![ops::testing::deno_test::init_ops(sender.clone())], Stdio { stdin: StdioPipe::Inherit, stdout, @@ -928,7 +932,119 @@ async fn test_specifier( ) .await?; - worker.run_test_specifier(mode).await + let mut coverage_collector = worker.maybe_setup_coverage_collector().await?; + + // We execute the main module as a side module so that import.meta.main is not set. + match worker.execute_side_module_possibly_with_npm().await { + Ok(()) => {} + Err(error) => { + if error.is::() { + sender.send(TestEvent::UncaughtError( + specifier.to_string(), + Box::new(error.downcast::().unwrap()), + ))?; + return Ok(()); + } else { + return Err(error); + } + } + } + + let mut worker = worker.into_main_worker(); + if ps.options.trace_ops() { + worker.js_runtime.execute_script_static( + located_script_name!(), + "Deno[Deno.internal].core.enableOpCallTracing();", + )?; + } + worker.dispatch_load_event(located_script_name!())?; + + let tests = { + let state_rc = worker.js_runtime.op_state(); + let mut state = state_rc.borrow_mut(); + std::mem::take(&mut state.borrow_mut::().0) + }; + let unfiltered = tests.len(); + let (only, no_only): (Vec<_>, Vec<_>) = + tests.into_iter().partition(|(d, _)| d.only); + let used_only = !only.is_empty(); + let tests = if used_only { only } else { no_only }; + let mut tests = tests + .into_iter() + .filter(|(d, _)| filter.includes(&d.name)) + .collect::>(); + if let Some(seed) = ps.options.shuffle_tests() { + tests.shuffle(&mut SmallRng::seed_from_u64(seed)); + } + sender.send(TestEvent::Plan(TestPlan { + origin: specifier.to_string(), + total: tests.len(), + filtered_out: unfiltered - tests.len(), + used_only, + }))?; + let mut had_uncaught_error = false; + for (desc, function) in tests { + if fail_fast_tracker.should_stop() { + break; + } + if desc.ignore { + sender.send(TestEvent::Result(desc.id, TestResult::Ignored, 0))?; + continue; + } + if had_uncaught_error { + sender.send(TestEvent::Result(desc.id, TestResult::Cancelled, 0))?; + continue; + } + sender.send(TestEvent::Wait(desc.id))?; + let earlier = SystemTime::now(); + let promise = { + let scope = &mut worker.js_runtime.handle_scope(); + let cb = function.open(scope); + let this = v8::undefined(scope).into(); + let promise = cb.call(scope, this, &[]).unwrap(); + v8::Global::new(scope, promise) + }; + let result = match worker.js_runtime.resolve_value(promise).await { + Ok(r) => r, + Err(error) => { + if error.is::() { + sender.send(TestEvent::UncaughtError( + specifier.to_string(), + Box::new(error.downcast::().unwrap()), + ))?; + fail_fast_tracker.add_failure(); + sender.send(TestEvent::Result(desc.id, TestResult::Cancelled, 0))?; + had_uncaught_error = true; + continue; + } else { + return Err(error); + } + } + }; + let scope = &mut worker.js_runtime.handle_scope(); + let result = v8::Local::new(scope, result); + let result = serde_v8::from_v8::(scope, result)?; + if matches!(result, TestResult::Failed(_)) { + fail_fast_tracker.add_failure(); + } + let elapsed = SystemTime::now().duration_since(earlier)?.as_millis(); + sender.send(TestEvent::Result(desc.id, result, elapsed as u64))?; + } + + loop { + if !worker.dispatch_beforeunload_event(located_script_name!())? { + break; + } + worker.run_event_loop(false).await?; + } + worker.dispatch_unload_event(located_script_name!())?; + + if let Some(coverage_collector) = coverage_collector.as_mut() { + worker + .with_event_loop(coverage_collector.stop_collecting().boxed_local()) + .await?; + } + Ok(()) } fn extract_files_from_regex_blocks( @@ -1182,18 +1298,18 @@ static HAS_TEST_RUN_SIGINT_HANDLER: AtomicBool = AtomicBool::new(false); async fn test_specifiers( ps: &ProcState, permissions: &Permissions, - specifiers_with_mode: Vec<(ModuleSpecifier, TestMode)>, + specifiers: Vec, options: TestSpecifierOptions, ) -> Result<(), AnyError> { let log_level = ps.options.log_level(); - let specifiers_with_mode = if let Some(seed) = ps.options.shuffle_tests() { + let specifiers = if let Some(seed) = ps.options.shuffle_tests() { let mut rng = SmallRng::seed_from_u64(seed); - let mut specifiers_with_mode = specifiers_with_mode; - specifiers_with_mode.sort_by_key(|(specifier, _)| specifier.clone()); - specifiers_with_mode.shuffle(&mut rng); - specifiers_with_mode + let mut specifiers = specifiers; + specifiers.sort(); + specifiers.shuffle(&mut rng); + specifiers } else { - specifiers_with_mode + specifiers }; let (sender, mut receiver) = unbounded_channel::(); @@ -1207,44 +1323,23 @@ async fn test_specifiers( }); HAS_TEST_RUN_SIGINT_HANDLER.store(true, Ordering::Relaxed); - let join_handles = - specifiers_with_mode - .into_iter() - .map(move |(specifier, mode)| { - let ps = ps.clone(); - let permissions = permissions.clone(); - let mut sender = sender.clone(); - let options = options.clone(); - let fail_fast_tracker = FailFastTracker::new(options.fail_fast); - - tokio::task::spawn_blocking(move || { - if fail_fast_tracker.should_stop() { - return Ok(()); - } - - let origin = specifier.to_string(); - let file_result = run_local(test_specifier( - &ps, - permissions, - specifier, - mode, - sender.clone(), - fail_fast_tracker, - options, - )); - if let Err(error) = file_result { - if error.is::() { - sender.send(TestEvent::UncaughtError( - origin, - Box::new(error.downcast::().unwrap()), - ))?; - } else { - return Err(error); - } - } - Ok(()) - }) - }); + let join_handles = specifiers.into_iter().map(move |specifier| { + let ps = ps.clone(); + let permissions = permissions.clone(); + let sender = sender.clone(); + let options = options.clone(); + let fail_fast_tracker = FailFastTracker::new(options.fail_fast); + tokio::task::spawn_blocking(move || { + run_local(test_specifier( + &ps, + permissions, + specifier, + sender.clone(), + fail_fast_tracker, + options.filter, + )) + }) + }); let join_stream = stream::iter(join_handles) .buffer_unordered(concurrent_jobs.get()) @@ -1310,7 +1405,7 @@ async fn test_specifiers( .push((description.clone(), failure.clone())); } TestResult::Cancelled => { - unreachable!("should be handled in TestEvent::UncaughtError"); + summary.failed += 1; } } reporter.report_result(description, &result, elapsed); @@ -1321,12 +1416,6 @@ async fn test_specifiers( reporter.report_uncaught_error(&origin, &error); summary.failed += 1; summary.uncaught_errors.push((origin.clone(), error)); - for desc in tests.values() { - if desc.origin == origin && tests_with_result.insert(desc.id) { - summary.failed += 1; - reporter.report_result(desc, &TestResult::Cancelled, 0); - } - } } TestEvent::StepRegister(description) => { @@ -1360,6 +1449,8 @@ async fn test_specifiers( &tests, &test_steps, ), + ignore: false, + only: false, origin: description.origin.clone(), location: description.location.clone(), }, @@ -1565,7 +1656,13 @@ pub async fn run_tests( test_specifiers( &ps, &permissions, - specifiers_with_mode, + specifiers_with_mode + .into_iter() + .filter_map(|(s, m)| match m { + TestMode::Documentation => None, + _ => Some(s), + }) + .collect(), TestSpecifierOptions { concurrent_jobs: test_options.concurrent_jobs, fail_fast: test_options.fail_fast, @@ -1729,7 +1826,13 @@ pub async fn run_tests_with_watch( test_specifiers( &ps, permissions, - specifiers_with_mode, + specifiers_with_mode + .into_iter() + .filter_map(|(s, m)| match m { + TestMode::Documentation => None, + _ => Some(s), + }) + .collect(), TestSpecifierOptions { concurrent_jobs: test_options.concurrent_jobs, fail_fast: test_options.fail_fast, diff --git a/cli/worker.rs b/cli/worker.rs index 26b70d9a5f..018bee7681 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -5,14 +5,10 @@ use std::rc::Rc; use std::sync::Arc; use deno_ast::ModuleSpecifier; -use deno_core::ascii_str; use deno_core::error::AnyError; use deno_core::futures::task::LocalFutureObj; use deno_core::futures::FutureExt; use deno_core::located_script_name; -use deno_core::serde_json::json; -use deno_core::serde_v8; -use deno_core::v8; use deno_core::Extension; use deno_core::ModuleId; use deno_runtime::colors; @@ -36,7 +32,6 @@ use crate::ops; use crate::proc_state::ProcState; use crate::tools; use crate::tools::coverage::CoverageCollector; -use crate::tools::test::TestMode; use crate::util::checksum; use crate::version; @@ -45,11 +40,6 @@ pub struct CliMainWorker { is_main_cjs: bool, worker: MainWorker, ps: ProcState, - - js_run_tests_callback: Option>, - js_run_benchmarks_callback: Option>, - js_enable_test_callback: Option>, - js_enable_bench_callback: Option>, } impl CliMainWorker { @@ -176,114 +166,14 @@ impl CliMainWorker { executor.execute().await } - pub async fn run_test_specifier( - &mut self, - mode: TestMode, - ) -> Result<(), AnyError> { - self.enable_test(); - - // Enable op call tracing in core to enable better debugging of op sanitizer - // failures. - if self.ps.options.trace_ops() { - self.worker.js_runtime.execute_script_static( - located_script_name!(), - "Deno[Deno.internal].core.enableOpCallTracing();", - )?; - } - - let mut maybe_coverage_collector = - self.maybe_setup_coverage_collector().await?; - - // We only execute the specifier as a module if it is tagged with TestMode::Module or - // TestMode::Both. - if mode != TestMode::Documentation { - // We execute the module module as a side module so that import.meta.main is not set. - self.execute_side_module_possibly_with_npm().await?; - } - - self.worker.dispatch_load_event(located_script_name!())?; - self.run_tests(&self.ps.options.shuffle_tests()).await?; - loop { - if !self - .worker - .dispatch_beforeunload_event(located_script_name!())? - { - break; - } - self.worker.run_event_loop(false).await?; - } - - self.worker.dispatch_unload_event(located_script_name!())?; - - if let Some(coverage_collector) = maybe_coverage_collector.as_mut() { - self - .worker - .with_event_loop(coverage_collector.stop_collecting().boxed_local()) - .await?; - } - Ok(()) - } - - pub async fn run_lsp_test_specifier( - &mut self, - mode: TestMode, - ) -> Result<(), AnyError> { - self.enable_test(); - - self.worker.execute_script( - located_script_name!(), - ascii_str!("Deno[Deno.internal].core.enableOpCallTracing();"), - )?; - - if mode != TestMode::Documentation { - // We execute the module module as a side module so that import.meta.main is not set. - self.execute_side_module_possibly_with_npm().await?; - } - - self.worker.dispatch_load_event(located_script_name!())?; - self.run_tests(&None).await?; - loop { - if !self - .worker - .dispatch_beforeunload_event(located_script_name!())? - { - break; - } - self.worker.run_event_loop(false).await?; - } - self.worker.dispatch_unload_event(located_script_name!())?; - Ok(()) - } - - pub async fn run_bench_specifier(&mut self) -> Result<(), AnyError> { - self.enable_bench(); - - // We execute the module module as a side module so that import.meta.main is not set. - self.execute_side_module_possibly_with_npm().await?; - - self.worker.dispatch_load_event(located_script_name!())?; - self.run_benchmarks().await?; - loop { - if !self - .worker - .dispatch_beforeunload_event(located_script_name!())? - { - break; - } - self.worker.run_event_loop(false).await?; - } - self.worker.dispatch_unload_event(located_script_name!())?; - Ok(()) - } - - async fn execute_main_module_possibly_with_npm( + pub async fn execute_main_module_possibly_with_npm( &mut self, ) -> Result<(), AnyError> { let id = self.worker.preload_main_module(&self.main_module).await?; self.evaluate_module_possibly_with_npm(id).await } - async fn execute_side_module_possibly_with_npm( + pub async fn execute_side_module_possibly_with_npm( &mut self, ) -> Result<(), AnyError> { let id = self.worker.preload_side_module(&self.main_module).await?; @@ -325,7 +215,7 @@ impl CliMainWorker { Ok(()) } - async fn maybe_setup_coverage_collector( + pub async fn maybe_setup_coverage_collector( &mut self, ) -> Result, AnyError> { if let Some(ref coverage_dir) = self.ps.options.coverage_dir() { @@ -343,61 +233,6 @@ impl CliMainWorker { Ok(None) } } - - /// Run tests declared with `Deno.test()`. Test events will be dispatched - /// by calling ops which are currently only implemented in the CLI crate. - pub async fn run_tests( - &mut self, - shuffle: &Option, - ) -> Result<(), AnyError> { - let promise = { - let scope = &mut self.worker.js_runtime.handle_scope(); - let cb = self.js_run_tests_callback.as_ref().unwrap().open(scope); - let this = v8::undefined(scope).into(); - let options = - serde_v8::to_v8(scope, json!({ "shuffle": shuffle })).unwrap(); - let promise = cb.call(scope, this, &[options]).unwrap(); - v8::Global::new(scope, promise) - }; - self.worker.js_runtime.resolve_value(promise).await?; - Ok(()) - } - - /// Run benches declared with `Deno.bench()`. Bench events will be dispatched - /// by calling ops which are currently only implemented in the CLI crate. - pub async fn run_benchmarks(&mut self) -> Result<(), AnyError> { - let promise = { - let scope = &mut self.worker.js_runtime.handle_scope(); - let cb = self - .js_run_benchmarks_callback - .as_ref() - .unwrap() - .open(scope); - let this = v8::undefined(scope).into(); - let promise = cb.call(scope, this, &[]).unwrap(); - v8::Global::new(scope, promise) - }; - self.worker.js_runtime.resolve_value(promise).await?; - Ok(()) - } - - /// Enable `Deno.test()`. If this isn't called before executing user code, - /// `Deno.test()` calls will noop. - pub fn enable_test(&mut self) { - let scope = &mut self.worker.js_runtime.handle_scope(); - let cb = self.js_enable_test_callback.as_ref().unwrap().open(scope); - let this = v8::undefined(scope).into(); - cb.call(scope, this, &[]).unwrap(); - } - - /// Enable `Deno.bench()`. If this isn't called before executing user code, - /// `Deno.bench()` calls will noop. - pub fn enable_bench(&mut self) { - let scope = &mut self.worker.js_runtime.handle_scope(); - let cb = self.js_enable_bench_callback.as_ref().unwrap().open(scope); - let this = v8::undefined(scope).into(); - cb.call(scope, this, &[]).unwrap(); - } } pub async fn create_main_worker( @@ -405,42 +240,16 @@ pub async fn create_main_worker( main_module: ModuleSpecifier, permissions: PermissionsContainer, ) -> Result { - create_main_worker_internal( - ps, - main_module, - permissions, - vec![], - Default::default(), - false, - ) - .await + create_custom_worker(ps, main_module, permissions, vec![], Default::default()) + .await } -pub async fn create_main_worker_for_test_or_bench( - ps: &ProcState, - main_module: ModuleSpecifier, - permissions: PermissionsContainer, - custom_extensions: Vec, - stdio: deno_runtime::deno_io::Stdio, -) -> Result { - create_main_worker_internal( - ps, - main_module, - permissions, - custom_extensions, - stdio, - true, - ) - .await -} - -async fn create_main_worker_internal( +pub async fn create_custom_worker( ps: &ProcState, main_module: ModuleSpecifier, permissions: PermissionsContainer, mut custom_extensions: Vec, stdio: deno_runtime::deno_io::Stdio, - bench_or_test: bool, ) -> Result { let (main_module, is_main_cjs) = if let Ok(package_ref) = NpmPackageReqReference::from_specifier(&main_module) @@ -552,59 +361,17 @@ async fn create_main_worker_internal( stdio, }; - let mut worker = MainWorker::bootstrap_from_options( + let worker = MainWorker::bootstrap_from_options( main_module.clone(), permissions, options, ); - let ( - js_run_tests_callback, - js_run_benchmarks_callback, - js_enable_test_callback, - js_enable_bench_callback, - ) = if bench_or_test { - let scope = &mut worker.js_runtime.handle_scope(); - let js_run_tests_callback = deno_core::JsRuntime::eval::( - scope, - "Deno[Deno.internal].testing.runTests", - ) - .unwrap(); - let js_run_benchmarks_callback = - deno_core::JsRuntime::eval::( - scope, - "Deno[Deno.internal].testing.runBenchmarks", - ) - .unwrap(); - let js_enable_tests_callback = deno_core::JsRuntime::eval::( - scope, - "Deno[Deno.internal].testing.enableTest", - ) - .unwrap(); - let js_enable_bench_callback = deno_core::JsRuntime::eval::( - scope, - "Deno[Deno.internal].testing.enableBench", - ) - .unwrap(); - ( - Some(v8::Global::new(scope, js_run_tests_callback)), - Some(v8::Global::new(scope, js_run_benchmarks_callback)), - Some(v8::Global::new(scope, js_enable_tests_callback)), - Some(v8::Global::new(scope, js_enable_bench_callback)), - ) - } else { - (None, None, None, None) - }; - Ok(CliMainWorker { main_module, is_main_cjs, worker, ps: ps.clone(), - js_run_tests_callback, - js_run_benchmarks_callback, - js_enable_test_callback, - js_enable_bench_callback, }) }