1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00
This commit is contained in:
Marvin Hagemeister 2024-10-10 01:37:56 +02:00
parent e4dd440a4d
commit e6c455c613
4 changed files with 172 additions and 46 deletions

View file

@ -10,6 +10,7 @@ const {
op_register_test_group,
op_test_group_pop,
op_register_test_group_lifecycle,
op_register_test_run_fn,
op_test_event_step_result_failed,
op_test_event_step_result_ignored,
op_test_event_step_result_ok,
@ -501,12 +502,6 @@ 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}
@ -527,6 +522,29 @@ function wrapTest(desc) {
globalThis.Deno.test = test;
/** @typedef {{ name: string, fn: () => any, only: boolean, ignore: boolean }} BddTest */
/** @typedef {() => unknown | Promise<unknown>} TestLifecycleFn */
/** @typedef {{ name: string, ignore: boolean, only: boolean, children: Array<TestGroup | BddTest>, beforeAll: TestLifecycleFn | null, afterAll: TestLifecycleFn | null, beforeEach: TestLifecycleFn | null, afterEach: TestLifecycleFn | null}} TestGroup */
const ROOT_TEST_GROUP = {
name: "__<root>__",
ignore: false,
only: false,
children: [],
beforeAll: null,
beforeEach: null,
afterAll: null,
afterEach: null,
};
/** @type {{ hasOnly: boolean, stack: TestGroup[], total: number }} */
const BDD_CONTEXT = {
hasOnly: false,
stack: [ROOT_TEST_GROUP],
total: 0,
};
/**
* @param {string} name
* @param {fn: () => any} fn
@ -557,6 +575,16 @@ function itInner(name, fn, ignore, only) {
}
};
/** @type {BddTest} */
const testDef = {
name,
fn: testFn,
ignore,
only,
};
BDD_CONTEXT.stack.at(-1).children.push(testDef);
BDD_CONTEXT.total++;
op_register_test(
testFn,
escapeName(name),
@ -583,6 +611,7 @@ function it(name, fn) {
* @param {() => any} fn
*/
it.only = (name, fn) => {
BDD_CONTEXT.hasOnly = true;
itInner(name, fn, false, true);
};
/**
@ -606,11 +635,25 @@ function describeInner(name, fn, ignore, only) {
return;
}
op_register_test_group(name, ignore, only);
const parent = BDD_CONTEXT.stack.at(-1);
/** @type {TestGroup} */
const group = {
name,
ignore,
only,
children: [],
beforeAll: null,
beforeEach: null,
afterAll: null,
afterEach: null,
};
parent.children.push(group);
BDD_CONTEXT.stack.push(group);
try {
fn();
} finally {
op_test_group_pop();
BDD_CONTEXT.stack.pop();
}
}
@ -626,6 +669,7 @@ function describe(name, fn) {
* @param {() => void} fn
*/
describe.only = (name, fn) => {
BDD_CONTEXT.hasOnly = true;
describeInner(name, fn, false, true);
};
/**
@ -637,65 +681,31 @@ 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) {
const location = core.currentUserCallSite();
op_register_test_group_lifecycle(
BEFORE_ALL,
fn,
location.fileName,
location.lineNumber,
location.columnNumber,
);
BDD_CONTEXT.stack.at(-1).beforeAll = fn;
}
/**
* @param {() => any} fn
*/
function afterAll(fn) {
const location = core.currentUserCallSite();
op_register_test_group_lifecycle(
AFTER_ALL,
fn,
location.fileName,
location.lineNumber,
location.columnNumber,
);
BDD_CONTEXT.stack.at(-1).afterAll = fn;
}
/**
* @param {() => any} fn
*/
function beforeEach(fn) {
const location = core.currentUserCallSite();
op_register_test_group_lifecycle(
BEFORE_EACH,
fn,
location.fileName,
location.lineNumber,
location.columnNumber,
);
BDD_CONTEXT.stack.at(-1).beforeEach = fn;
}
/**
* @param {() => any} fn
*/
function afterEach(fn) {
const location = core.currentUserCallSite();
op_register_test_group_lifecycle(
AFTER_EACH,
fn,
location.fileName,
location.lineNumber,
location.columnNumber,
);
BDD_CONTEXT.stack.at(-1).afterEach = fn;
}
globalThis.before = beforeAll;
@ -706,3 +716,86 @@ globalThis.beforeEach = beforeEach;
globalThis.afterEach = afterEach;
globalThis.it = it;
globalThis.describe = describe;
/**
* This function is called from Rust.
* @param {bigint} seed
* @param {...any} rest
*/
async function runTests(seed, ...rest) {
console.log("RUN TESTS", seed, rest, ROOT_TEST_GROUP);
// Filter tests
await runGroup(seed, ROOT_TEST_GROUP);
}
/**
* @param {bigint} seed
* @param {TestGroup} group
*/
async function runGroup(seed, group) {
// Bail out if group has no tests or sub groups
/** @type {BddTest[]} */
const tests = [];
/** @type {TestGroup[]} */
const groups = [];
for (let i = 0; i < group.children[i]; i++) {
const child = group.children[i];
if ("beforeAll" in child) {
groups.push(child);
} else {
tests.push(child);
}
}
if (seed > 0) {
shuffle(tests, seed);
shuffle(groups, seed);
}
await group.beforeAll?.();
for (let i = 0; i < tests.length; i++) {
const test = tests[i];
await group.beforeEach?.();
await test.fn();
await group.afterEach?.();
}
for (let i = 0; i < groups.length; i++) {
const childGroup = groups[i];
await group.beforeEach?.();
await runGroup(seed, childGroup);
await group.afterEach?.();
}
await group.afterAll?.();
}
/**
* @template T
* @param {T[]} arr
* @param {bigint} seed
*/
function shuffle(arr, seed) {
let m = arr.length;
let t;
let i;
while (m) {
i = Math.floor(seed * m--);
t = arr[m];
arr[m] = arr[i];
arr[i] = t;
}
}
// No-op if we're not running in `deno test` subcommand.
if (typeof op_register_test === "function") {
op_register_test_run_fn(runTests);
}

View file

@ -33,6 +33,7 @@ deno_core::extension!(deno_test,
op_register_test_group,
op_test_group_pop,
op_register_test_group_lifecycle,
op_register_test_run_fn,
op_test_get_origin,
op_test_event_step_wait,
op_test_event_step_result_ok,
@ -171,6 +172,16 @@ fn op_test_group_pop(state: &mut OpState) -> Result<(), AnyError> {
Ok(())
}
#[op2]
fn op_register_test_run_fn(
state: &mut OpState,
#[global] function: v8::Global<v8::Function>,
) -> Result<(), AnyError> {
let container = state.borrow_mut::<TestContainer>();
container.run_fn = Some(function);
Ok(())
}
#[allow(clippy::too_many_arguments)]
#[op2]
fn op_register_test_group_lifecycle(

View file

@ -224,6 +224,7 @@ pub struct TestLocation {
#[derive(Default)]
pub(crate) struct TestContainer {
has_tests: bool,
pub run_fn: Option<v8::Global<v8::Function>>,
pub has_only: bool,
pub groups: Vec<TestGroup>,
pub stack: Vec<usize>,
@ -249,6 +250,7 @@ impl TestContainer {
Self {
has_tests: false,
run_fn: None,
groups: vec![root],
has_only: false,
stack,
@ -899,6 +901,25 @@ pub async fn run_tests_for_worker(
eprintln!("{:#?}", tc.stack);
let to_run: Vec<TestRunItem> = vec![];
if let Some(function) = &tc.run_fn {
let seed = if let Some(seed) = options.shuffle {
seed
} else {
0
};
let args = {
let scope = &mut worker.js_runtime.handle_scope();
let seed_value: v8::Local<v8::Value> =
v8::BigInt::new_from_u64(scope, seed as u64).into();
[v8::Global::new(scope, seed_value)]
};
let call = worker.js_runtime.call_with_args(&function, &args);
let result = worker
.js_runtime
.with_event_loop_promise(call, PollEventLoopOptions::default())
.await;
}
if let Some(seed) = options.shuffle {
// tests_to_run.shuffle(&mut SmallRng::seed_from_u64(seed));

View file

@ -486,6 +486,7 @@ const NOT_IMPORTED_OPS = [
"op_register_test_group",
"op_test_group_pop",
"op_register_test_group_lifecycle",
"op_register_test_run_fn",
"op_test_get_origin",
"op_pledge_test_permissions",