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 13:54:58 +02:00
parent e6c455c613
commit 777c935603
11 changed files with 339 additions and 225 deletions

View file

@ -1,5 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// @ts-check
import { core, primordials } from "ext:core/mod.js"; import { core, primordials } from "ext:core/mod.js";
import { escapeName, withPermissions } from "ext:cli/40_test_common.js"; import { escapeName, withPermissions } from "ext:cli/40_test_common.js";
@ -7,14 +9,18 @@ import { escapeName, withPermissions } from "ext:cli/40_test_common.js";
const { const {
op_register_test_step, op_register_test_step,
op_register_test, op_register_test,
op_register_test_group, op_test_group_register,
op_test_group_pop, op_test_group_event_start,
op_register_test_group_lifecycle, op_test_group_event_end,
op_register_test_run_fn, op_register_test_run_fn,
op_test_event_step_result_failed, op_test_event_step_result_failed,
op_test_event_step_result_ignored, op_test_event_step_result_ignored,
op_test_event_step_result_ok, op_test_event_step_result_ok,
op_test_event_step_wait, op_test_event_step_wait,
op_test_event_start,
op_test_event_result_ok,
op_test_event_result_ignored,
op_test_event_result_failed,
op_test_get_origin, op_test_get_origin,
} = core.ops; } = core.ops;
const { const {
@ -44,11 +50,11 @@ const DenoNs = globalThis.Deno;
* origin: string, * origin: string,
* location: TestLocation, * location: TestLocation,
* ignore: boolean, * ignore: boolean,
* only: boolean. * only: boolean,
* sanitizeOps: boolean, * sanitizeOps: boolean,
* sanitizeResources: boolean, * sanitizeResources: boolean,
* sanitizeExit: boolean, * sanitizeExit: boolean,
* permissions: PermissionOptions, * permissions: Deno.PermissionOptions,
* }} TestDescription * }} TestDescription
* *
* @typedef {{ * @typedef {{
@ -86,9 +92,9 @@ const DenoNs = globalThis.Deno;
* fn: BenchFunction * fn: BenchFunction
* origin: string, * origin: string,
* ignore: boolean, * ignore: boolean,
* only: boolean. * only: boolean,
* sanitizeExit: boolean, * sanitizeExit: boolean,
* permissions: PermissionOptions, * permissions: Deno.PermissionOptions,
* }} BenchDescription * }} BenchDescription
*/ */
@ -149,7 +155,7 @@ function wrapOuter(fn, desc) {
} }
function wrapInner(fn) { function wrapInner(fn) {
/** @param desc {TestDescription | TestStepDescription} */ /** @param {TestDescription | TestStepDescription} desc */
return async function innerWrapped(desc) { return async function innerWrapped(desc) {
function getRunningStepDescs() { function getRunningStepDescs() {
const results = []; const results = [];
@ -210,7 +216,16 @@ function wrapInner(fn) {
const registerTestIdRetBuf = new Uint32Array(1); const registerTestIdRetBuf = new Uint32Array(1);
const registerTestIdRetBufU8 = new Uint8Array(registerTestIdRetBuf.buffer); const registerTestIdRetBufU8 = new Uint8Array(registerTestIdRetBuf.buffer);
// As long as we're using one isolate per test, we can cache the origin since it won't change const registerTestGroupIdRetBuf = new Uint32Array(1);
const registerTestGroupIdRetBufU8 = new Uint8Array(
registerTestGroupIdRetBuf.buffer,
);
/**
* As long as we're using one isolate per test, we can cache the origin
* since it won't change.
* @type {string | undefined}
*/
let cachedOrigin = undefined; let cachedOrigin = undefined;
function testInner( function testInner(
@ -384,7 +399,7 @@ function stepReportResult(desc, result, elapsed) {
} }
} }
/** @param desc {TestDescription | TestStepDescription} */ /** @param {TestDescription | TestStepDescription} desc */
function createTestContext(desc) { function createTestContext(desc) {
let parent; let parent;
let level; let level;
@ -416,8 +431,8 @@ function createTestContext(desc) {
*/ */
origin: desc.origin, origin: desc.origin,
/** /**
* @param nameOrFnOrOptions {string | TestStepDefinition | ((t: TestContext) => void | Promise<void>)} * @param {string | TestStepDescription | ((t: TestContext) => void | Promise<void>)} nameOrFnOrOptions
* @param maybeFn {((t: TestContext) => void | Promise<void>) | undefined} * @param {((t: TestContext) => void | Promise<void>) | undefined} maybeFn
*/ */
async step(nameOrFnOrOptions, maybeFn) { async step(nameOrFnOrOptions, maybeFn) {
if (MapPrototypeGet(testStates, desc.id).completed) { if (MapPrototypeGet(testStates, desc.id).completed) {
@ -504,9 +519,8 @@ function createTestContext(desc) {
/** /**
* Wrap a user test function in one which returns a structured result. * Wrap a user test function in one which returns a structured result.
* @template T {Function} * @template {Function} T
* @param testFn {T} * @param {TestDescription | TestStepDescription} desc
* @param desc {TestDescription | TestStepDescription}
* @returns {T} * @returns {T}
*/ */
function wrapTest(desc) { function wrapTest(desc) {
@ -522,14 +536,34 @@ function wrapTest(desc) {
globalThis.Deno.test = test; globalThis.Deno.test = test;
/** @typedef {{ name: string, fn: () => any, only: boolean, ignore: boolean }} BddTest */ /**
* @typedef {{
/** @typedef {() => unknown | Promise<unknown>} TestLifecycleFn */ * id: number,
* name: string,
/** @typedef {{ name: string, ignore: boolean, only: boolean, children: Array<TestGroup | BddTest>, beforeAll: TestLifecycleFn | null, afterAll: TestLifecycleFn | null, beforeEach: TestLifecycleFn | null, afterEach: TestLifecycleFn | null}} TestGroup */ * fn: () => any,
* only: boolean,
* ignore: boolean
* }} BddTest
*
* @typedef {() => unknown | Promise<unknown>} TestLifecycleFn
*
* @typedef {{
* id: number,
* name: string,
* ignore: boolean,
* only: boolean,
* children: Array<TestGroup | BddTest>,
* beforeAll: TestLifecycleFn | null,
* afterAll: TestLifecycleFn | null,
* beforeEach: TestLifecycleFn | null,
* afterEach: TestLifecycleFn | null
* }} TestGroup
*/
/** @type {TestGroup} */
const ROOT_TEST_GROUP = { const ROOT_TEST_GROUP = {
name: "__<root>__", id: 0,
name: "__DENO_TEST_ROOT__",
ignore: false, ignore: false,
only: false, only: false,
children: [], children: [],
@ -538,6 +572,16 @@ const ROOT_TEST_GROUP = {
afterAll: null, afterAll: null,
afterEach: null, afterEach: null,
}; };
// No-op if we're not running in `deno test` subcommand.
if (typeof op_register_test === "function") {
op_test_group_register(
registerTestGroupIdRetBufU8,
ROOT_TEST_GROUP.name,
true,
);
ROOT_TEST_GROUP.id = registerTestGroupIdRetBuf[0];
}
/** @type {{ hasOnly: boolean, stack: TestGroup[], total: number }} */ /** @type {{ hasOnly: boolean, stack: TestGroup[], total: number }} */
const BDD_CONTEXT = { const BDD_CONTEXT = {
hasOnly: false, hasOnly: false,
@ -547,14 +591,16 @@ const BDD_CONTEXT = {
/** /**
* @param {string} name * @param {string} name
* @param {fn: () => any} fn * @param {() => any} fn
* @param {boolean} ignore * @param {boolean} ignore
* @param {boolean} only * @param {boolean} only
*/ */
function itInner(name, fn, ignore, only) { function itInner(name, fn, ignore, only) {
// No-op if we're not running in `deno test` subcommand. if (
if (typeof op_register_test !== "function") { !ignore && BDD_CONTEXT.stack.length > 1 &&
return; BDD_CONTEXT.stack.some((x) => x.ignore)
) {
ignore = true;
} }
if (cachedOrigin == undefined) { if (cachedOrigin == undefined) {
@ -577,12 +623,13 @@ function itInner(name, fn, ignore, only) {
/** @type {BddTest} */ /** @type {BddTest} */
const testDef = { const testDef = {
id: 0,
name, name,
fn: testFn, fn: testFn,
ignore, ignore,
only, only,
}; };
BDD_CONTEXT.stack.at(-1).children.push(testDef); getGroupParent().children.push(testDef);
BDD_CONTEXT.total++; BDD_CONTEXT.total++;
op_register_test( op_register_test(
@ -597,6 +644,8 @@ function itInner(name, fn, ignore, only) {
location.columnNumber, location.columnNumber,
registerTestIdRetBufU8, registerTestIdRetBufU8,
); );
testDef.id = registerTestIdRetBuf[0];
} }
/** /**
@ -623,6 +672,18 @@ it.ignore = (name, fn) => {
}; };
it.skip = it.ignore; it.skip = it.ignore;
/** @type {(x: TestGroup | BddTest) => x is TestGroup} */
function isTestGroup(x) {
return "beforeAll" in x;
}
/**
* @returns {TestGroup}
*/
function getGroupParent() {
return /** @type {TestGroup} */ (BDD_CONTEXT.stack.at(-1));
}
/** /**
* @param {string} name * @param {string} name
* @param {() => void} fn * @param {() => void} fn
@ -635,9 +696,13 @@ function describeInner(name, fn, ignore, only) {
return; return;
} }
const parent = BDD_CONTEXT.stack.at(-1); op_test_group_register(registerTestGroupIdRetBufU8, name, false);
const id = registerTestGroupIdRetBuf[0];
const parent = getGroupParent();
/** @type {TestGroup} */ /** @type {TestGroup} */
const group = { const group = {
id,
name, name,
ignore, ignore,
only, only,
@ -653,6 +718,43 @@ function describeInner(name, fn, ignore, only) {
try { try {
fn(); fn();
} finally { } finally {
let allIgnore = true;
let onlyChildCount = 0;
for (let i = 0; i < group.children.length; i++) {
const child = group.children[i];
if (!child.ignore) allIgnore = false;
if (!isTestGroup(child) && child.only) {
onlyChildCount++;
}
}
if (!group.ignore) {
group.ignore = allIgnore;
}
if (!group.ignore) {
if (onlyChildCount > 0) {
group.only = true;
if (onlyChildCount < group.children.length - 1) {
for (let i = 0; i < group.children.length; i++) {
const child = group.children[i];
if (!isTestGroup(child) && !child.only) {
child.ignore = true;
}
}
}
} else if (group.only) {
for (let i = 0; i < group.children.length; i++) {
const child = group.children[i];
child.only = true;
}
}
}
BDD_CONTEXT.stack.pop(); BDD_CONTEXT.stack.pop();
} }
} }
@ -685,27 +787,27 @@ describe.skip = describe.ignore;
* @param {() => any} fn * @param {() => any} fn
*/ */
function beforeAll(fn) { function beforeAll(fn) {
BDD_CONTEXT.stack.at(-1).beforeAll = fn; getGroupParent().beforeAll = fn;
} }
/** /**
* @param {() => any} fn * @param {() => any} fn
*/ */
function afterAll(fn) { function afterAll(fn) {
BDD_CONTEXT.stack.at(-1).afterAll = fn; getGroupParent().afterAll = fn;
} }
/** /**
* @param {() => any} fn * @param {() => any} fn
*/ */
function beforeEach(fn) { function beforeEach(fn) {
BDD_CONTEXT.stack.at(-1).beforeEach = fn; getGroupParent().beforeEach = fn;
} }
/** /**
* @param {() => any} fn * @param {() => any} fn
*/ */
function afterEach(fn) { function afterEach(fn) {
BDD_CONTEXT.stack.at(-1).afterEach = fn; getGroupParent().afterEach = fn;
} }
globalThis.before = beforeAll; globalThis.before = beforeAll;
@ -719,68 +821,109 @@ globalThis.describe = describe;
/** /**
* This function is called from Rust. * This function is called from Rust.
* @param {bigint} seed * @param {number} seed
* @param {...any} rest * @param {...any} rest
*/ */
async function runTests(seed, ...rest) { async function runTests(seed, ...rest) {
if (BDD_CONTEXT.hasOnly) {
ROOT_TEST_GROUP.only = ROOT_TEST_GROUP.children.some((child) => child.only);
}
console.log("RUN TESTS", seed, rest, ROOT_TEST_GROUP); console.log("RUN TESTS", seed, rest, ROOT_TEST_GROUP);
try {
// Filter tests await runGroup(seed, ROOT_TEST_GROUP);
} finally {
await runGroup(seed, ROOT_TEST_GROUP); //
}
} }
/** /**
* @param {bigint} seed * @param {number} seed
* @param {TestGroup} group * @param {TestGroup} group
*/ */
async function runGroup(seed, group) { async function runGroup(seed, group) {
// Bail out if group has no tests or sub groups op_test_group_event_start(group.id);
/** @type {BddTest[]} */ if (seed > 0 && group.children.length > 1) {
const tests = []; shuffle(group.children, seed);
/** @type {TestGroup[]} */ }
const groups = [];
for (let i = 0; i < group.children[i]; i++) { // Sort tests:
const child = group.children[i]; // - non-ignored tests first (might be shuffled earlier)
if ("beforeAll" in child) { // - ignored tests second
groups.push(child); // - groups last
} else { group.children.sort(sortTestItems);
tests.push(child);
// Short circuit if the whole group is ignored
if (group.ignore) {
// FIXME
return;
}
try {
if (group.beforeAll !== null) {
await group.beforeAll();
} }
for (let i = 0; i < group.children.length; i++) {
const child = group.children[i];
if (group.beforeEach !== null) {
await group.beforeEach();
}
if (isTestGroup(child)) {
await runGroup(seed, child);
} else if (child.ignore) {
op_test_event_result_ignored(child.id);
} else {
op_test_event_start(child.id);
const start = DateNow();
try {
await child.fn();
const elapsed = DateNow() - start;
op_test_event_result_ok(child.id, elapsed);
} catch (err) {
const elapsed = DateNow() - start;
op_test_event_result_failed(child.id, elapsed);
}
}
if (group.afterEach !== null) {
await group.afterEach();
}
}
if (group.afterAll !== null) {
await group.afterAll();
}
} finally {
op_test_group_event_end(group.id);
} }
}
if (seed > 0) { /**
shuffle(tests, seed); * @param {TestGroup | BddTest} a
shuffle(groups, seed); * @param {TestGroup | BddTest} b
} */
function sortTestItems(a, b) {
const isAGroup = isTestGroup(a);
const isBGroup = isTestGroup(b);
if (isAGroup && isBGroup) return 0;
if (isAGroup && !isBGroup) return -1;
if (!isAGroup && isBGroup) return 1;
await group.beforeAll?.(); if (a.ignore && b.ignore) return 0;
if (a.ignore && !b.ignore) return -1;
if (!a.ignore && b.ignore) return 1;
for (let i = 0; i < tests.length; i++) { return 0;
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 * @template T
* @param {T[]} arr * @param {T[]} arr
* @param {bigint} seed * @param {number} seed
*/ */
function shuffle(arr, seed) { function shuffle(arr, seed) {
let m = arr.length; let m = arr.length;
@ -788,13 +931,22 @@ function shuffle(arr, seed) {
let i; let i;
while (m) { while (m) {
i = Math.floor(seed * m--); i = Math.floor(randomize(seed) * m--);
t = arr[m]; t = arr[m];
arr[m] = arr[i]; arr[m] = arr[i];
arr[i] = t; arr[i] = t;
} }
} }
/**
* @param {number} seed
* @returns {number}
*/
function randomize(seed) {
const x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
}
// No-op if we're not running in `deno test` subcommand. // No-op if we're not running in `deno test` subcommand.
if (typeof op_register_test === "function") { if (typeof op_register_test === "function") {
op_register_test_run_fn(runTests); op_register_test_run_fn(runTests);

View file

@ -421,6 +421,7 @@ impl TestRun {
} }
test::TestEvent::ForceEndReport => {} test::TestEvent::ForceEndReport => {}
test::TestEvent::Sigint => {} test::TestEvent::Sigint => {}
_ => {} // FIXME
} }
} }

View file

@ -5,9 +5,9 @@ use crate::tools::test::TestDescription;
use crate::tools::test::TestEvent; use crate::tools::test::TestEvent;
use crate::tools::test::TestEventSender; use crate::tools::test::TestEventSender;
use crate::tools::test::TestFailure; use crate::tools::test::TestFailure;
use crate::tools::test::TestGroup; use crate::tools::test::TestGroupDescription;
use crate::tools::test::TestGroupLifecycleFn;
use crate::tools::test::TestLocation; use crate::tools::test::TestLocation;
use crate::tools::test::TestResult;
use crate::tools::test::TestStepDescription; use crate::tools::test::TestStepDescription;
use crate::tools::test::TestStepResult; use crate::tools::test::TestStepResult;
@ -30,10 +30,14 @@ deno_core::extension!(deno_test,
op_restore_test_permissions, op_restore_test_permissions,
op_register_test, op_register_test,
op_register_test_step, op_register_test_step,
op_register_test_group,
op_test_group_pop,
op_register_test_group_lifecycle,
op_register_test_run_fn, op_register_test_run_fn,
op_test_group_register,
op_test_group_event_start,
op_test_group_event_end,
op_test_event_start,
op_test_event_result_ignored,
op_test_event_result_ok,
op_test_event_result_failed,
op_test_get_origin, op_test_get_origin,
op_test_event_step_wait, op_test_event_step_wait,
op_test_event_step_result_ok, op_test_event_step_result_ok,
@ -93,7 +97,7 @@ pub fn op_restore_test_permissions(
} }
static NEXT_ID: AtomicUsize = AtomicUsize::new(0); static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
static NEXT_GROUP_ID: AtomicUsize = AtomicUsize::new(1); static NEXT_GROUP_ID: AtomicUsize = AtomicUsize::new(0);
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
#[op2] #[op2]
@ -120,7 +124,6 @@ fn op_register_test(
let origin = state.borrow::<ModuleSpecifier>().to_string(); let origin = state.borrow::<ModuleSpecifier>().to_string();
let description = TestDescription { let description = TestDescription {
id, id,
parent_id: 0,
name, name,
ignore, ignore,
only, only,
@ -140,35 +143,37 @@ fn op_register_test(
} }
#[op2(fast)] #[op2(fast)]
fn op_register_test_group( fn op_test_group_register(
state: &mut OpState, state: &mut OpState,
#[buffer] ret_buf: &mut [u8],
#[string] name: String, #[string] name: String,
ignore: bool, is_root: bool,
only: bool,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let id = NEXT_GROUP_ID.fetch_add(1, Ordering::SeqCst); let id = NEXT_GROUP_ID.fetch_add(1, Ordering::SeqCst);
let container = state.borrow_mut::<TestContainer>(); let sender = state.borrow_mut::<TestEventSender>();
let description = TestGroupDescription { id, name, is_root };
let group = TestGroup { sender.send(TestEvent::GroupRegister(description)).ok();
id, ret_buf.copy_from_slice(&(id as u32).to_le_bytes());
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(()) Ok(())
} }
#[op2(fast)] #[op2(fast)]
fn op_test_group_pop(state: &mut OpState) -> Result<(), AnyError> { fn op_test_group_event_start(
let container = state.borrow_mut::<TestContainer>(); state: &mut OpState,
container.group_pop(); #[smi] id: usize,
) -> Result<(), AnyError> {
let sender = state.borrow_mut::<TestEventSender>();
sender.send(TestEvent::GroupWait(id)).ok();
Ok(())
}
#[op2(fast)]
fn op_test_group_event_end(
state: &mut OpState,
#[smi] id: usize,
) -> Result<(), AnyError> {
let sender = state.borrow_mut::<TestEventSender>();
sender.send(TestEvent::GroupResult(id)).ok();
Ok(()) Ok(())
} }
@ -182,43 +187,6 @@ fn op_register_test_run_fn(
Ok(()) 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] #[op2]
#[string] #[string]
fn op_test_get_origin(state: &mut OpState) -> String { fn op_test_get_origin(state: &mut OpState) -> String {
@ -260,6 +228,46 @@ fn op_register_test_step(
Ok(id) Ok(id)
} }
#[op2(fast)]
fn op_test_event_start(state: &mut OpState, #[smi] id: usize) {
let sender = state.borrow_mut::<TestEventSender>();
sender.send(TestEvent::Wait(id)).ok();
}
#[op2(fast)]
fn op_test_event_result_ignored(state: &mut OpState, #[smi] id: usize) {
let sender = state.borrow_mut::<TestEventSender>();
sender
.send(TestEvent::Result(id, TestResult::Ignored, 0))
.ok();
}
#[op2(fast)]
fn op_test_event_result_ok(
state: &mut OpState,
#[smi] id: usize,
#[smi] duration: u64,
) {
let sender = state.borrow_mut::<TestEventSender>();
sender
.send(TestEvent::Result(id, TestResult::Ok, duration))
.ok();
}
#[op2(fast)]
fn op_test_event_result_failed(
state: &mut OpState,
#[smi] id: usize,
#[smi] duration: u64,
) {
// FIXME: Placeholder
let failure = TestFailure::IncompleteSteps;
let sender = state.borrow_mut::<TestEventSender>();
sender
.send(TestEvent::Result(id, TestResult::Failed(failure), duration))
.ok();
}
#[op2(fast)] #[op2(fast)]
fn op_test_event_step_wait(state: &mut OpState, #[smi] id: usize) { fn op_test_event_step_wait(state: &mut OpState, #[smi] id: usize) {
let sender = state.borrow_mut::<TestEventSender>(); let sender = state.borrow_mut::<TestEventSender>();

View file

@ -226,34 +226,15 @@ pub(crate) struct TestContainer {
has_tests: bool, has_tests: bool,
pub run_fn: Option<v8::Global<v8::Function>>, pub run_fn: Option<v8::Global<v8::Function>>,
pub has_only: bool, pub has_only: bool,
pub groups: Vec<TestGroup>,
pub stack: Vec<usize>,
pub tests: (TestDescriptions, Vec<v8::Global<v8::Function>>), pub tests: (TestDescriptions, Vec<v8::Global<v8::Function>>),
} }
impl TestContainer { impl TestContainer {
pub fn new() -> Self { 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 { Self {
has_tests: false, has_tests: false,
run_fn: None, run_fn: None,
groups: vec![root],
has_only: false, has_only: false,
stack,
tests: ( tests: (
TestDescriptions { TestDescriptions {
..Default::default() ..Default::default()
@ -265,7 +246,7 @@ impl TestContainer {
pub fn register( pub fn register(
&mut self, &mut self,
mut description: TestDescription, description: TestDescription,
function: v8::Global<v8::Function>, function: v8::Global<v8::Function>,
) { ) {
self.has_tests = true; self.has_tests = true;
@ -274,35 +255,10 @@ impl TestContainer {
self.has_only = true 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.0.tests.insert(description.id, description);
self.tests.1.push(function); 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 { pub fn is_empty(&self) -> bool {
self.has_tests self.has_tests
} }
@ -336,7 +292,6 @@ impl<'a> IntoIterator for &'a TestDescriptions {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TestDescription { pub struct TestDescription {
pub id: usize, pub id: usize,
pub parent_id: usize,
pub name: String, pub name: String,
pub ignore: bool, pub ignore: bool,
pub only: bool, pub only: bool,
@ -346,6 +301,14 @@ pub struct TestDescription {
pub sanitize_resources: bool, pub sanitize_resources: bool,
} }
#[derive(Debug, Clone, PartialEq, Deserialize, Eq, Hash)]
#[serde(rename_all = "camelCase")]
pub struct TestGroupDescription {
pub id: usize,
pub name: String,
pub is_root: bool,
}
/// May represent a failure of a test or test step. /// May represent a failure of a test or test step.
#[derive(Debug, Clone, PartialEq, Deserialize, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Deserialize, Eq, Hash)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -367,32 +330,6 @@ 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)] #[derive(Debug, Default, Clone, PartialEq)]
pub struct TestFailureFormatOptions { pub struct TestFailureFormatOptions {
pub hide_stacktraces: bool, pub hide_stacktraces: bool,
@ -561,6 +498,9 @@ pub enum TestStdioStream {
pub enum TestEvent { pub enum TestEvent {
Register(Arc<TestDescriptions>), Register(Arc<TestDescriptions>),
Plan(TestPlan), Plan(TestPlan),
GroupRegister(TestGroupDescription),
GroupWait(usize),
GroupResult(usize),
Wait(usize), Wait(usize),
Output(Vec<u8>), Output(Vec<u8>),
Slow(usize, u64), Slow(usize, u64),
@ -881,12 +821,6 @@ pub fn send_test_event(
) )
} }
enum TestRunItem {
Lifecycle(usize),
Group(usize),
Test(usize),
}
pub async fn run_tests_for_worker( pub async fn run_tests_for_worker(
worker: &mut MainWorker, worker: &mut MainWorker,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
@ -898,9 +832,9 @@ pub async fn run_tests_for_worker(
let tc = let tc =
std::mem::take(&mut *state_rc.borrow_mut().borrow_mut::<TestContainer>()); std::mem::take(&mut *state_rc.borrow_mut().borrow_mut::<TestContainer>());
eprintln!("{:#?}", tc.stack); let tests: Arc<TestDescriptions> = tc.tests.0.into();
send_test_event(&state_rc, TestEvent::Register(tests.clone()))?;
let to_run: Vec<TestRunItem> = vec![];
if let Some(function) = &tc.run_fn { if let Some(function) = &tc.run_fn {
let seed = if let Some(seed) = options.shuffle { let seed = if let Some(seed) = options.shuffle {
seed seed
@ -911,14 +845,16 @@ pub async fn run_tests_for_worker(
let args = { let args = {
let scope = &mut worker.js_runtime.handle_scope(); let scope = &mut worker.js_runtime.handle_scope();
let seed_value: v8::Local<v8::Value> = let seed_value: v8::Local<v8::Value> =
v8::BigInt::new_from_u64(scope, seed as u64).into(); v8::Number::new(scope, seed as f64).into();
[v8::Global::new(scope, seed_value)] [v8::Global::new(scope, seed_value)]
}; };
let call = worker.js_runtime.call_with_args(&function, &args); let call = worker.js_runtime.call_with_args(&function, &args);
// FIXME: result
let result = worker let result = worker
.js_runtime .js_runtime
.with_event_loop_promise(call, PollEventLoopOptions::default()) .with_event_loop_promise(call, PollEventLoopOptions::default())
.await; .await;
return Ok(());
} }
if let Some(seed) = options.shuffle { if let Some(seed) = options.shuffle {
@ -926,11 +862,7 @@ pub async fn run_tests_for_worker(
} }
// FILTER // FILTER
eprintln!("sorted, {:#?}", tc.stack);
let test_functions = tc.tests.1; 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( let res = run_tests_for_worker_inner(
worker, worker,
specifier, specifier,
@ -1401,6 +1333,9 @@ pub async fn report_tests(
while let Some((_, event)) = receiver.recv().await { while let Some((_, event)) = receiver.recv().await {
match event { match event {
TestEvent::GroupRegister(description) => {
reporter.report_register_group(&description);
}
TestEvent::Register(description) => { TestEvent::Register(description) => {
for (_, description) in description.into_iter() { for (_, description) in description.into_iter() {
reporter.report_register(description); reporter.report_register(description);
@ -1489,6 +1424,7 @@ pub async fn report_tests(
} }
std::process::exit(130); std::process::exit(130);
} }
_ => {}
} }
} }

View file

@ -13,6 +13,12 @@ impl CompoundTestReporter {
} }
impl TestReporter for CompoundTestReporter { impl TestReporter for CompoundTestReporter {
fn report_register_group(&mut self, description: &TestGroupDescription) {
for reporter in &mut self.test_reporters {
reporter.report_register_group(description)
}
}
fn report_register(&mut self, description: &TestDescription) { fn report_register(&mut self, description: &TestDescription) {
for reporter in &mut self.test_reporters { for reporter in &mut self.test_reporters {
reporter.report_register(description); reporter.report_register(description);

View file

@ -88,6 +88,7 @@ fn fmt_cancelled() -> String {
#[allow(clippy::print_stdout)] #[allow(clippy::print_stdout)]
impl TestReporter for DotTestReporter { impl TestReporter for DotTestReporter {
fn report_register_group(&mut self, _description: &TestGroupDescription) {}
fn report_register(&mut self, _description: &TestDescription) {} fn report_register(&mut self, _description: &TestDescription) {}
fn report_plan(&mut self, plan: &TestPlan) { fn report_plan(&mut self, plan: &TestPlan) {

View file

@ -80,6 +80,8 @@ impl JunitTestReporter {
} }
impl TestReporter for JunitTestReporter { impl TestReporter for JunitTestReporter {
fn report_register_group(&mut self, _description: &TestGroupDescription) {}
fn report_register(&mut self, description: &TestDescription) { fn report_register(&mut self, description: &TestDescription) {
let mut case = quick_junit::TestCase::new( let mut case = quick_junit::TestCase::new(
description.name.clone(), description.name.clone(),

View file

@ -17,6 +17,7 @@ pub use tap::TapTestReporter;
pub trait TestReporter { pub trait TestReporter {
fn report_register(&mut self, description: &TestDescription); fn report_register(&mut self, description: &TestDescription);
fn report_register_group(&mut self, description: &TestGroupDescription);
fn report_plan(&mut self, plan: &TestPlan); fn report_plan(&mut self, plan: &TestPlan);
fn report_wait(&mut self, description: &TestDescription); fn report_wait(&mut self, description: &TestDescription);
fn report_slow(&mut self, description: &TestDescription, elapsed: u64); fn report_slow(&mut self, description: &TestDescription, elapsed: u64);

View file

@ -167,6 +167,8 @@ impl PrettyTestReporter {
} }
impl TestReporter for PrettyTestReporter { impl TestReporter for PrettyTestReporter {
fn report_register_group(&mut self, _description: &TestGroupDescription) {}
fn report_register(&mut self, _description: &TestDescription) {} fn report_register(&mut self, _description: &TestDescription) {}
fn report_plan(&mut self, plan: &TestPlan) { fn report_plan(&mut self, plan: &TestPlan) {
self.write_output_end(); self.write_output_end();

View file

@ -123,6 +123,7 @@ impl TapTestReporter {
#[allow(clippy::print_stdout)] #[allow(clippy::print_stdout)]
impl TestReporter for TapTestReporter { impl TestReporter for TapTestReporter {
fn report_register_group(&mut self, _description: &TestGroupDescription) {}
fn report_register(&mut self, _description: &TestDescription) {} fn report_register(&mut self, _description: &TestDescription) {}
fn report_plan(&mut self, plan: &TestPlan) { fn report_plan(&mut self, plan: &TestPlan) {

View file

@ -476,6 +476,10 @@ const NOT_IMPORTED_OPS = [
"op_test_event_step_result_ignored", "op_test_event_step_result_ignored",
"op_test_event_step_result_ok", "op_test_event_step_result_ok",
"op_test_event_step_wait", "op_test_event_step_wait",
"op_test_event_start",
"op_test_event_result_ignored",
"op_test_event_result_ok",
"op_test_event_result_failed",
"op_test_op_sanitizer_collect", "op_test_op_sanitizer_collect",
"op_test_op_sanitizer_finish", "op_test_op_sanitizer_finish",
"op_test_op_sanitizer_get_async_message", "op_test_op_sanitizer_get_async_message",
@ -483,9 +487,9 @@ const NOT_IMPORTED_OPS = [
"op_restore_test_permissions", "op_restore_test_permissions",
"op_register_test_step", "op_register_test_step",
"op_register_test", "op_register_test",
"op_register_test_group", "op_test_group_register",
"op_test_group_pop", "op_test_group_event_start",
"op_register_test_group_lifecycle", "op_test_group_event_end",
"op_register_test_run_fn", "op_register_test_run_fn",
"op_test_get_origin", "op_test_get_origin",
"op_pledge_test_permissions", "op_pledge_test_permissions",