mirror of
https://github.com/denoland/deno.git
synced 2025-01-11 16:42:21 -05:00
WIP
This commit is contained in:
parent
5214346993
commit
e4dd440a4d
5 changed files with 311 additions and 138 deletions
|
@ -7,6 +7,9 @@ import { escapeName, withPermissions } from "ext:cli/40_test_common.js";
|
|||
const {
|
||||
op_register_test_step,
|
||||
op_register_test,
|
||||
op_register_test_group,
|
||||
op_test_group_pop,
|
||||
op_register_test_group_lifecycle,
|
||||
op_test_event_step_result_failed,
|
||||
op_test_event_step_result_ignored,
|
||||
op_test_event_step_result_ok,
|
||||
|
@ -27,7 +30,6 @@ const {
|
|||
} = primordials;
|
||||
|
||||
import { setExitHandler } from "ext:runtime/30_os.js";
|
||||
import console from "node:console";
|
||||
|
||||
// Capture `Deno` global so that users deleting or mangling it, won't
|
||||
// have impact on our sanitizers.
|
||||
|
@ -499,6 +501,12 @@ function createTestContext(desc) {
|
|||
};
|
||||
}
|
||||
|
||||
/** @type { only: boolean[], ignore: boolean[] } */
|
||||
const bddStack = {
|
||||
only: [],
|
||||
ignore: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrap a user test function in one which returns a structured result.
|
||||
* @template T {Function}
|
||||
|
@ -519,31 +527,6 @@ function wrapTest(desc) {
|
|||
|
||||
globalThis.Deno.test = test;
|
||||
|
||||
/**
|
||||
* @typedef {{ name: string, kind: "group" | "test", ignore: boolean, children: BddItem[], fn: null | (() => any), parent: BddItem | null, beforeAll: null | (() => void | Promise<void>), afterAll: null | (() => void | Promise<void>), beforeEach: null | (() => void | Promise<void>), afterEach: null | (() => void | Promise<void>) }} BddItem
|
||||
*/
|
||||
|
||||
/** @type {BddItem} */
|
||||
const BDD_ROOT = {
|
||||
name: "__<root>__",
|
||||
kind: "group",
|
||||
ignore: false,
|
||||
only: false,
|
||||
fn: null,
|
||||
children: [],
|
||||
parent: null,
|
||||
beforeAll: null,
|
||||
beforeEach: null,
|
||||
afterAll: null,
|
||||
afterEach: null,
|
||||
};
|
||||
|
||||
/** @type {BddItem[]} */
|
||||
const bddStack = [BDD_ROOT];
|
||||
|
||||
/** @type {BddItem[]} */
|
||||
const onlys = [];
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {fn: () => any} fn
|
||||
|
@ -551,23 +534,41 @@ const onlys = [];
|
|||
* @param {boolean} only
|
||||
*/
|
||||
function itInner(name, fn, ignore, only) {
|
||||
const parent = bddStack.at(-1);
|
||||
/** @type {BddItem} */
|
||||
const item = {
|
||||
kind: "test",
|
||||
name,
|
||||
fn,
|
||||
// No-op if we're not running in `deno test` subcommand.
|
||||
if (typeof op_register_test !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cachedOrigin == undefined) {
|
||||
cachedOrigin = op_test_get_origin();
|
||||
}
|
||||
|
||||
const location = core.currentUserCallSite();
|
||||
const sanitizeOps = false;
|
||||
const sanitizeResources = false;
|
||||
const testFn = async () => {
|
||||
if (ignore) return "ignored";
|
||||
|
||||
try {
|
||||
await fn();
|
||||
return "ok";
|
||||
} catch (error) {
|
||||
return { failed: { jsError: core.destructureError(error) } };
|
||||
}
|
||||
};
|
||||
|
||||
op_register_test(
|
||||
testFn,
|
||||
escapeName(name),
|
||||
ignore,
|
||||
only,
|
||||
children: [],
|
||||
parent,
|
||||
beforeAll: null,
|
||||
beforeEach: null,
|
||||
afterAll: null,
|
||||
afterEach: null,
|
||||
};
|
||||
if (only) onlys.push(item);
|
||||
parent.children.push(item);
|
||||
sanitizeOps,
|
||||
sanitizeResources,
|
||||
location.fileName,
|
||||
location.lineNumber,
|
||||
location.columnNumber,
|
||||
registerTestIdRetBufU8,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -600,28 +601,16 @@ it.skip = it.ignore;
|
|||
* @param {boolean} only
|
||||
*/
|
||||
function describeInner(name, fn, ignore, only) {
|
||||
const parent = bddStack.at(-1);
|
||||
/** @type {BddItem} */
|
||||
const item = {
|
||||
name,
|
||||
kind: "group",
|
||||
fn: null,
|
||||
ignore,
|
||||
only,
|
||||
children: [],
|
||||
parent,
|
||||
beforeAll: null,
|
||||
beforeEach: null,
|
||||
afterAll: null,
|
||||
afterEach: null,
|
||||
};
|
||||
if (only) onlys.push(item);
|
||||
parent.children.push(item);
|
||||
bddStack.push(item);
|
||||
// No-op if we're not running in `deno test` subcommand.
|
||||
if (typeof op_register_test !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
op_register_test_group(name, ignore, only);
|
||||
try {
|
||||
fn();
|
||||
} finally {
|
||||
bddStack.pop();
|
||||
op_test_group_pop();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -648,29 +637,65 @@ describe.ignore = (name, fn) => {
|
|||
};
|
||||
describe.skip = describe.ignore;
|
||||
|
||||
// Keep in sync on the rust side
|
||||
const BEFORE_ALL = 1;
|
||||
const BEFORE_EACH = 2;
|
||||
const AFTER_ALL = 3;
|
||||
const AFTER_EACH = 4;
|
||||
|
||||
/**
|
||||
* @param {() => any} fn
|
||||
*/
|
||||
function beforeAll(fn) {
|
||||
bddStack.at(-1).beforeAll = fn;
|
||||
const location = core.currentUserCallSite();
|
||||
op_register_test_group_lifecycle(
|
||||
BEFORE_ALL,
|
||||
fn,
|
||||
location.fileName,
|
||||
location.lineNumber,
|
||||
location.columnNumber,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {() => any} fn
|
||||
*/
|
||||
function afterAll(fn) {
|
||||
bddStack.at(-1).afterAll = fn;
|
||||
const location = core.currentUserCallSite();
|
||||
op_register_test_group_lifecycle(
|
||||
AFTER_ALL,
|
||||
fn,
|
||||
location.fileName,
|
||||
location.lineNumber,
|
||||
location.columnNumber,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {() => any} fn
|
||||
*/
|
||||
function beforeEach(fn) {
|
||||
bddStack.at(-1).beforeEach = fn;
|
||||
const location = core.currentUserCallSite();
|
||||
op_register_test_group_lifecycle(
|
||||
BEFORE_EACH,
|
||||
fn,
|
||||
location.fileName,
|
||||
location.lineNumber,
|
||||
location.columnNumber,
|
||||
);
|
||||
}
|
||||
/**
|
||||
* @param {() => any} fn
|
||||
*/
|
||||
function afterEach(fn) {
|
||||
bddStack.at(-1).afterEach = fn;
|
||||
const location = core.currentUserCallSite();
|
||||
op_register_test_group_lifecycle(
|
||||
AFTER_EACH,
|
||||
fn,
|
||||
location.fileName,
|
||||
location.lineNumber,
|
||||
location.columnNumber,
|
||||
);
|
||||
}
|
||||
|
||||
globalThis.before = beforeAll;
|
||||
|
@ -681,66 +706,3 @@ globalThis.beforeEach = beforeEach;
|
|||
globalThis.afterEach = afterEach;
|
||||
globalThis.it = it;
|
||||
globalThis.describe = describe;
|
||||
|
||||
/**
|
||||
* @param {BddItem} root
|
||||
*/
|
||||
async function runBddTests(root) {
|
||||
if (onlys.length > 0) {
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
|
||||
await runGroup(root);
|
||||
console.log({ onlys });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BddItem} item
|
||||
*/
|
||||
function getLabel(item) {
|
||||
let name = item.name;
|
||||
|
||||
let tmp = item.parent;
|
||||
while (tmp !== null && tmp !== BDD_ROOT) {
|
||||
name = `${tmp.name} > ${name}`;
|
||||
tmp = tmp.parent;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BddItem} group
|
||||
*/
|
||||
async function runGroup(group) {
|
||||
await group.beforeAll?.();
|
||||
|
||||
for (let i = 0; i < group.children.length; i++) {
|
||||
await group.beforeEach?.();
|
||||
|
||||
const child = group.children[i];
|
||||
if (child.kind === "test") {
|
||||
const name = getLabel(child);
|
||||
console.log("running:", name);
|
||||
|
||||
await child.fn();
|
||||
} else {
|
||||
await runGroup(child);
|
||||
}
|
||||
|
||||
await group.afterEach?.();
|
||||
}
|
||||
|
||||
await group.afterAll?.();
|
||||
}
|
||||
|
||||
// Check if we're running the `deno test` command
|
||||
if (typeof op_register_test === "function") {
|
||||
// Wait a tick and check if there are any bdd tests to run
|
||||
setTimeout(async () => {
|
||||
if (bddStack.length > 0) {
|
||||
await runBddTests(BDD_ROOT);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ use crate::tools::test::TestDescription;
|
|||
use crate::tools::test::TestEvent;
|
||||
use crate::tools::test::TestEventSender;
|
||||
use crate::tools::test::TestFailure;
|
||||
use crate::tools::test::TestGroup;
|
||||
use crate::tools::test::TestGroupLifecycleFn;
|
||||
use crate::tools::test::TestLocation;
|
||||
use crate::tools::test::TestStepDescription;
|
||||
use crate::tools::test::TestStepResult;
|
||||
|
@ -28,6 +30,9 @@ deno_core::extension!(deno_test,
|
|||
op_restore_test_permissions,
|
||||
op_register_test,
|
||||
op_register_test_step,
|
||||
op_register_test_group,
|
||||
op_test_group_pop,
|
||||
op_register_test_group_lifecycle,
|
||||
op_test_get_origin,
|
||||
op_test_event_step_wait,
|
||||
op_test_event_step_result_ok,
|
||||
|
@ -39,7 +44,7 @@ deno_core::extension!(deno_test,
|
|||
},
|
||||
state = |state, options| {
|
||||
state.put(options.sender);
|
||||
state.put(TestContainer::default());
|
||||
state.put(TestContainer::new());
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -87,6 +92,7 @@ pub fn op_restore_test_permissions(
|
|||
}
|
||||
|
||||
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
|
||||
static NEXT_GROUP_ID: AtomicUsize = AtomicUsize::new(1);
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[op2]
|
||||
|
@ -113,6 +119,7 @@ fn op_register_test(
|
|||
let origin = state.borrow::<ModuleSpecifier>().to_string();
|
||||
let description = TestDescription {
|
||||
id,
|
||||
parent_id: 0,
|
||||
name,
|
||||
ignore,
|
||||
only,
|
||||
|
@ -131,6 +138,76 @@ fn op_register_test(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[op2(fast)]
|
||||
fn op_register_test_group(
|
||||
state: &mut OpState,
|
||||
#[string] name: String,
|
||||
ignore: bool,
|
||||
only: bool,
|
||||
) -> Result<(), AnyError> {
|
||||
let id = NEXT_GROUP_ID.fetch_add(1, Ordering::SeqCst);
|
||||
let container = state.borrow_mut::<TestContainer>();
|
||||
|
||||
let group = TestGroup {
|
||||
id,
|
||||
parent_id: 0,
|
||||
name,
|
||||
ignore,
|
||||
only,
|
||||
children: vec![],
|
||||
after_all: None,
|
||||
after_each: None,
|
||||
before_all: None,
|
||||
before_each: None,
|
||||
};
|
||||
container.register_group(group);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[op2(fast)]
|
||||
fn op_test_group_pop(state: &mut OpState) -> Result<(), AnyError> {
|
||||
let container = state.borrow_mut::<TestContainer>();
|
||||
container.group_pop();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[op2]
|
||||
fn op_register_test_group_lifecycle(
|
||||
state: &mut OpState,
|
||||
#[smi] kind: u32,
|
||||
#[global] function: v8::Global<v8::Function>,
|
||||
#[string] file_name: String,
|
||||
#[smi] line_number: u32,
|
||||
#[smi] column_number: u32,
|
||||
) -> Result<(), AnyError> {
|
||||
let container = state.borrow_mut::<TestContainer>();
|
||||
|
||||
let lifecycle = TestGroupLifecycleFn {
|
||||
function,
|
||||
location: TestLocation {
|
||||
column_number,
|
||||
line_number,
|
||||
file_name,
|
||||
},
|
||||
};
|
||||
|
||||
// Keep in sync with the JS side
|
||||
if let Some(last_id) = container.stack.last() {
|
||||
let last: &mut TestGroup =
|
||||
container.groups.get_mut::<usize>(*last_id).unwrap();
|
||||
match kind {
|
||||
1 => last.before_all = Some(lifecycle),
|
||||
2 => last.before_each = Some(lifecycle),
|
||||
3 => last.after_all = Some(lifecycle),
|
||||
4 => last.after_each = Some(lifecycle),
|
||||
_ => panic!("Unknown test group lifecycle kind"),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[op2]
|
||||
#[string]
|
||||
fn op_test_get_origin(state: &mut OpState) -> String {
|
||||
|
|
|
@ -222,23 +222,87 @@ pub struct TestLocation {
|
|||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct TestContainer(
|
||||
TestDescriptions,
|
||||
Vec<v8::Global<v8::Function>>,
|
||||
);
|
||||
pub(crate) struct TestContainer {
|
||||
has_tests: bool,
|
||||
pub has_only: bool,
|
||||
pub groups: Vec<TestGroup>,
|
||||
pub stack: Vec<usize>,
|
||||
pub tests: (TestDescriptions, Vec<v8::Global<v8::Function>>),
|
||||
}
|
||||
|
||||
impl TestContainer {
|
||||
pub fn new() -> Self {
|
||||
let root = TestGroup {
|
||||
id: 0,
|
||||
parent_id: 0,
|
||||
children: vec![],
|
||||
ignore: false,
|
||||
name: "<root>".to_string(),
|
||||
only: false,
|
||||
after_all: None,
|
||||
after_each: None,
|
||||
before_all: None,
|
||||
before_each: None,
|
||||
};
|
||||
|
||||
let stack = vec![root.id];
|
||||
|
||||
Self {
|
||||
has_tests: false,
|
||||
groups: vec![root],
|
||||
has_only: false,
|
||||
stack,
|
||||
tests: (
|
||||
TestDescriptions {
|
||||
..Default::default()
|
||||
},
|
||||
vec![],
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(
|
||||
&mut self,
|
||||
description: TestDescription,
|
||||
mut description: TestDescription,
|
||||
function: v8::Global<v8::Function>,
|
||||
) {
|
||||
self.0.tests.insert(description.id, description);
|
||||
self.1.push(function)
|
||||
self.has_tests = true;
|
||||
|
||||
if description.only {
|
||||
self.has_only = true
|
||||
}
|
||||
|
||||
if let Some(last_id) = self.stack.last() {
|
||||
description.parent_id = *last_id;
|
||||
|
||||
let last: &mut TestGroup =
|
||||
self.groups.get_mut::<usize>(*last_id).unwrap();
|
||||
last.children.push(TestGroupChild::Test(description.id));
|
||||
}
|
||||
|
||||
self.tests.0.tests.insert(description.id, description);
|
||||
self.tests.1.push(function);
|
||||
}
|
||||
|
||||
pub fn register_group(&mut self, mut group: TestGroup) {
|
||||
if let Some(last_id) = self.stack.last() {
|
||||
group.parent_id = *last_id;
|
||||
|
||||
let last: &mut TestGroup =
|
||||
self.groups.get_mut::<usize>(*last_id).unwrap();
|
||||
last.children.push(TestGroupChild::Group(group.id));
|
||||
}
|
||||
|
||||
self.stack.push(group.id);
|
||||
self.groups.push(group);
|
||||
}
|
||||
|
||||
pub fn group_pop(&mut self) {
|
||||
self.stack.pop();
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.1.is_empty()
|
||||
self.has_tests
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -270,6 +334,7 @@ impl<'a> IntoIterator for &'a TestDescriptions {
|
|||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TestDescription {
|
||||
pub id: usize,
|
||||
pub parent_id: usize,
|
||||
pub name: String,
|
||||
pub ignore: bool,
|
||||
pub only: bool,
|
||||
|
@ -300,6 +365,32 @@ impl From<&TestDescription> for TestFailureDescription {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TestGroupLifecycleFn {
|
||||
pub function: v8::Global<v8::Function>,
|
||||
pub location: TestLocation,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct TestGroup {
|
||||
pub id: usize,
|
||||
pub parent_id: usize,
|
||||
pub name: String,
|
||||
pub ignore: bool,
|
||||
pub only: bool,
|
||||
pub children: Vec<TestGroupChild>,
|
||||
pub before_all: Option<TestGroupLifecycleFn>,
|
||||
pub before_each: Option<TestGroupLifecycleFn>,
|
||||
pub after_all: Option<TestGroupLifecycleFn>,
|
||||
pub after_each: Option<TestGroupLifecycleFn>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TestGroupChild {
|
||||
Group(usize),
|
||||
Test(usize),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
pub struct TestFailureFormatOptions {
|
||||
pub hide_stacktraces: bool,
|
||||
|
@ -788,6 +879,12 @@ pub fn send_test_event(
|
|||
)
|
||||
}
|
||||
|
||||
enum TestRunItem {
|
||||
Lifecycle(usize),
|
||||
Group(usize),
|
||||
Test(usize),
|
||||
}
|
||||
|
||||
pub async fn run_tests_for_worker(
|
||||
worker: &mut MainWorker,
|
||||
specifier: &ModuleSpecifier,
|
||||
|
@ -796,10 +893,22 @@ pub async fn run_tests_for_worker(
|
|||
) -> Result<(), AnyError> {
|
||||
let state_rc = worker.js_runtime.op_state();
|
||||
// Take whatever tests have been registered
|
||||
let TestContainer(tests, test_functions) =
|
||||
let tc =
|
||||
std::mem::take(&mut *state_rc.borrow_mut().borrow_mut::<TestContainer>());
|
||||
|
||||
let tests: Arc<TestDescriptions> = tests.into();
|
||||
eprintln!("{:#?}", tc.stack);
|
||||
|
||||
let to_run: Vec<TestRunItem> = vec![];
|
||||
|
||||
if let Some(seed) = options.shuffle {
|
||||
// tests_to_run.shuffle(&mut SmallRng::seed_from_u64(seed));
|
||||
}
|
||||
// FILTER
|
||||
|
||||
eprintln!("sorted, {:#?}", tc.stack);
|
||||
|
||||
let test_functions = tc.tests.1;
|
||||
let tests: Arc<TestDescriptions> = tc.tests.0.into();
|
||||
send_test_event(&state_rc, TestEvent::Register(tests.clone()))?;
|
||||
let res = run_tests_for_worker_inner(
|
||||
worker,
|
||||
|
|
22
cli/tsc/dts/lib.deno.ns.d.ts
vendored
22
cli/tsc/dts/lib.deno.ns.d.ts
vendored
|
@ -142,6 +142,28 @@ interface PerformanceMeasureOptions {
|
|||
end?: string | number;
|
||||
}
|
||||
|
||||
interface BddTestFunction {
|
||||
(name: string, fn: () => void | Promise<void>): void;
|
||||
only(name: string, fn: () => void | Promise<void>): void;
|
||||
ignore(name: string, fn: () => void | Promise<void>): void;
|
||||
skip(name: string, fn: () => void | Promise<void>): void;
|
||||
}
|
||||
declare const it: BddTestFunction;
|
||||
|
||||
interface BddTestGroup {
|
||||
(name: string, fn: () => void | Promise<void>): void;
|
||||
only(name: string, fn: () => void | Promise<void>): void;
|
||||
ignore(name: string, fn: () => void | Promise<void>): void;
|
||||
skip(name: string, fn: () => void | Promise<void>): void;
|
||||
}
|
||||
declare const describe: BddTestGroup;
|
||||
|
||||
type BddLifecycleFn = (fn: () => unknown) => void;
|
||||
declare const beforeAll: BddLifecycleFn;
|
||||
declare const beforeEach: BddLifecycleFn;
|
||||
declare const afterAll: BddLifecycleFn;
|
||||
declare const afterEach: BddLifecycleFn;
|
||||
|
||||
/** The global namespace where Deno specific, non-standard APIs are located. */
|
||||
declare namespace Deno {
|
||||
/** A set of error constructors that are raised by Deno APIs.
|
||||
|
|
|
@ -483,6 +483,9 @@ const NOT_IMPORTED_OPS = [
|
|||
"op_restore_test_permissions",
|
||||
"op_register_test_step",
|
||||
"op_register_test",
|
||||
"op_register_test_group",
|
||||
"op_test_group_pop",
|
||||
"op_register_test_group_lifecycle",
|
||||
"op_test_get_origin",
|
||||
"op_pledge_test_permissions",
|
||||
|
||||
|
|
Loading…
Reference in a new issue