1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-28 16:20:57 -05:00

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.
This commit is contained in:
Nayeem Rahman 2023-04-13 18:43:23 +01:00 committed by Levente Kurusa
parent 2b35ddfc46
commit 67b1b0f183
No known key found for this signature in database
GPG key ID: 9F72F3C05BA137C4
10 changed files with 450 additions and 705 deletions

View file

@ -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;
/** 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;

View file

@ -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");
}

View file

@ -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();
}

View file

@ -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()
}

View file

@ -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])

View file

@ -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])

View file

@ -1,4 +1,3 @@
running 0 tests from ./test/text.md
ok | 0 passed | 0 failed ([WILDCARD])

View file

@ -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)
})
});

View file

@ -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,

View file

@ -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,
})
}