summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
authorNayeem Rahman <nayeemrmn99@gmail.com>2023-04-13 18:43:23 +0100
committerGitHub <noreply@github.com>2023-04-13 19:43:23 +0200
commit6e8618ae0f55bcaa4cfaaa579b4e21f9f74b117d (patch)
treedc0facd89b255b2bffe51b33920e46cb2a5d55d1 /cli
parent4e53bc5a94a66858e9c141c7d807a8c9caa93403 (diff)
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.
Diffstat (limited to 'cli')
-rw-r--r--cli/js/40_testing.js330
-rw-r--r--cli/lsp/testing/execution.rs77
-rw-r--r--cli/ops/bench.rs54
-rw-r--r--cli/ops/testing.rs79
-rw-r--r--cli/tests/testdata/test/doc_only.out1
-rw-r--r--cli/tests/testdata/test/shuffle.out30
-rw-r--r--cli/tests/testdata/test/text.out1
-rw-r--r--cli/tools/bench.rs87
-rw-r--r--cli/tools/test.rs237
-rw-r--r--cli/worker.rs247
10 files changed, 444 insertions, 699 deletions
diff --git a/cli/js/40_testing.js b/cli/js/40_testing.js
index babbec8c2..a0dcaf499 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<number, TestState | TestStepState>} */
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;
-
- try {
- if (desc.permissions) {
- token = pledgePermissions(desc.permissions);
- }
+/** 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;
- 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 });
});
- }
-
- 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);
- }
-}
+ if (desc.permissions) {
+ token = pledgePermissions(desc.permissions);
+ }
-let origin = null;
+ if (desc.sanitizeExit) {
+ setExitHandler((exitCode) => {
+ assert(
+ false,
+ `Bench attempted to exit with exit code: ${exitCode}`,
+ );
+ });
+ }
-function getTestOrigin() {
- if (origin == null) {
- origin = ops.op_get_test_origin();
- }
- return origin;
-}
+ const benchTimeInMs = 500;
+ const stats = await benchMeasure(benchTimeInMs, fn, desc.async);
-function getBenchOrigin() {
- if (origin == null) {
- origin = ops.op_get_bench_origin();
- }
- return origin;
+ return { ok: stats };
+ } catch (error) {
+ return { failed: core.destructureError(error) };
+ } finally {
+ globalThis.console = originalConsole;
+ if (bench.sanitizeExit) setExitHandler(null);
+ if (token !== null) restorePermissions(token);
+ }
+ };
}
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 466c0d942..020dd5c08 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::<JsError>() {
sender.send(test::TestEvent::UncaughtError(
- origin.clone(),
+ origin,
Box::new(error.downcast::<JsError>().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 86498cd7c..da0f3d959 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<v8::Function>)>,
+);
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<BenchEvent>,
- 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::<ModuleSpecifier>().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<String>,
+ 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<BenchRegisterResult, AnyError> {
let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
- let filter = state.borrow::<TestFilter>().clone();
- let filtered_out = !filter.includes(&info.name);
+ let origin = state.borrow::<ModuleSpecifier>().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<v8::Function> = info.function.v8_value.try_into()?;
+ let function = v8::Global::new(scope, function);
+ state
+ .borrow_mut::<BenchContainer>()
+ .0
+ .push((description.clone(), function));
let sender = state.borrow::<UnboundedSender<BenchEvent>>().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::<UnboundedSender<BenchEvent>>().clone();
sender.send(event).ok();
}
diff --git a/cli/ops/testing.rs b/cli/ops/testing.rs
index 8b5c95fea..e36d7e611 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<v8::Function>)>,
+);
+
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<String, AnyError> {
- Ok(state.borrow::<ModuleSpecifier>().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<TestRegisterResult, AnyError> {
let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
- let filter = state.borrow::<TestFilter>().clone();
- let filtered_out = !filter.includes(&info.name);
+ let origin = state.borrow::<ModuleSpecifier>().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<v8::Function> = info.function.v8_value.try_into()?;
+ let function = v8::Global::new(scope, function);
+ state
+ .borrow_mut::<TestContainer>()
+ .0
+ .push((description.clone(), function));
let mut sender = state.borrow::<TestEventSender>().clone();
sender.send(TestEvent::Register(description)).ok();
- Ok(TestRegisterResult { id, filtered_out })
+ Ok(TestRegisterResult { id, origin })
}
fn deserialize_parent<'de, D>(deserializer: D) -> Result<usize, D::Error>
@@ -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<TestRegisterResult, AnyError> {
let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
+ let origin = state.borrow::<ModuleSpecifier>().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::<TestEventSender>().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::<FailFastTracker>().add_failure();
- }
+ assert!(
+ matches!(event, TestEvent::StepWait(_) | TestEvent::StepResult(..)),
+ "Only step wait/result events are expected from JS."
+ );
let mut sender = state.borrow::<TestEventSender>().clone();
sender.send(event).ok();
Ok(())
}
-
-#[op]
-fn op_tests_should_stop(state: &mut OpState) -> bool {
- state.borrow::<FailFastTracker>().should_stop()
-}
diff --git a/cli/tests/testdata/test/doc_only.out b/cli/tests/testdata/test/doc_only.out
index a6cb89df0..2b8b6dc73 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 28bd97d5c..fdc2ca9ed 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 8 ... ok ([WILDCARD])
+test 0 ... ok ([WILDCARD])
+test 9 ... ok ([WILDCARD])
test 4 ... ok ([WILDCARD])
+test 6 ... ok ([WILDCARD])
test 1 ... ok ([WILDCARD])
-test 0 ... 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 8 ... ok ([WILDCARD])
+test 0 ... ok ([WILDCARD])
+test 9 ... ok ([WILDCARD])
test 4 ... ok ([WILDCARD])
+test 6 ... ok ([WILDCARD])
test 1 ... ok ([WILDCARD])
-test 0 ... 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 8 ... ok ([WILDCARD])
+test 0 ... ok ([WILDCARD])
+test 9 ... ok ([WILDCARD])
test 4 ... ok ([WILDCARD])
+test 6 ... ok ([WILDCARD])
test 1 ... ok ([WILDCARD])
-test 0 ... 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 354dc24b5..f1b7f7d01 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 f20aca8e2..da80f5b7e 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<String>,
+ 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<BenchEvent>,
- options: BenchSpecifierOptions,
+ sender: UnboundedSender<BenchEvent>,
+ 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::<ops::bench::BenchContainer>().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::<Vec<_>>();
+ let mut groups = IndexSet::<Option<String>>::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::<BenchResult>(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 7d6a6baa4..f90a68561 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::<JsError>() {
+ sender.send(TestEvent::UncaughtError(
+ specifier.to_string(),
+ Box::new(error.downcast::<JsError>().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::<ops::testing::TestContainer>().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::<Vec<_>>();
+ 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::<JsError>() {
+ sender.send(TestEvent::UncaughtError(
+ specifier.to_string(),
+ Box::new(error.downcast::<JsError>().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::<TestResult>(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<ModuleSpecifier>,
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::<TestEvent>();
@@ -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::<JsError>() {
- sender.send(TestEvent::UncaughtError(
- origin,
- Box::new(error.downcast::<JsError>().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 26b70d9a5..018bee768 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<v8::Global<v8::Function>>,
- js_run_benchmarks_callback: Option<v8::Global<v8::Function>>,
- js_enable_test_callback: Option<v8::Global<v8::Function>>,
- js_enable_bench_callback: Option<v8::Global<v8::Function>>,
}
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<Option<CoverageCollector>, 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<u64>,
- ) -> 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<CliMainWorker, AnyError> {
- 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<Extension>,
- stdio: deno_runtime::deno_io::Stdio,
-) -> Result<CliMainWorker, AnyError> {
- 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<Extension>,
stdio: deno_runtime::deno_io::Stdio,
- bench_or_test: bool,
) -> Result<CliMainWorker, AnyError> {
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::<v8::Function>(
- scope,
- "Deno[Deno.internal].testing.runTests",
- )
- .unwrap();
- let js_run_benchmarks_callback =
- deno_core::JsRuntime::eval::<v8::Function>(
- scope,
- "Deno[Deno.internal].testing.runBenchmarks",
- )
- .unwrap();
- let js_enable_tests_callback = deno_core::JsRuntime::eval::<v8::Function>(
- scope,
- "Deno[Deno.internal].testing.enableTest",
- )
- .unwrap();
- let js_enable_bench_callback = deno_core::JsRuntime::eval::<v8::Function>(
- 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,
})
}