diff options
Diffstat (limited to 'runtime/js/40_testing.js')
-rw-r--r-- | runtime/js/40_testing.js | 753 |
1 files changed, 311 insertions, 442 deletions
diff --git a/runtime/js/40_testing.js b/runtime/js/40_testing.js index 48f2fefb8..fabdb8dc4 100644 --- a/runtime/js/40_testing.js +++ b/runtime/js/40_testing.js @@ -14,25 +14,20 @@ ArrayPrototypeMap, ArrayPrototypePush, ArrayPrototypeShift, - ArrayPrototypeSome, ArrayPrototypeSort, DateNow, Error, FunctionPrototype, Map, + MapPrototypeGet, MapPrototypeHas, + MapPrototypeSet, MathCeil, ObjectKeys, ObjectPrototypeIsPrototypeOf, Promise, - RegExp, - RegExpPrototypeTest, SafeArrayIterator, Set, - StringPrototypeEndsWith, - StringPrototypeIncludes, - StringPrototypeSlice, - StringPrototypeStartsWith, SymbolToStringTag, TypeError, } = window.__bootstrap.primordials; @@ -139,12 +134,12 @@ // ops. Note that "unref" ops are ignored since in nature that are // optional. function assertOps(fn) { - /** @param step {TestStep} */ - return async function asyncOpSanitizer(step) { + /** @param desc {TestDescription | TestStepDescription} */ + return async function asyncOpSanitizer(desc) { const pre = core.metrics(); const preTraces = new Map(core.opCallTraces); try { - await fn(step); + await fn(desc); } finally { // Defer until next event loop turn - that way timeouts and intervals // cleared can actually be removed from resource table, otherwise @@ -152,7 +147,7 @@ await opSanitizerDelay(); } - if (step.shouldSkipSanitizers) return; + if (shouldSkipSanitizers(desc)) return; const post = core.metrics(); const postTraces = new Map(core.opCallTraces); @@ -366,15 +361,13 @@ // Wrap test function in additional assertion that makes sure // the test case does not "leak" resources - ie. resource table after // the test has exactly the same contents as before the test. - function assertResources( - fn, - ) { - /** @param step {TestStep} */ - return async function resourceSanitizer(step) { + function assertResources(fn) { + /** @param desc {TestDescription | TestStepDescription} */ + return async function resourceSanitizer(desc) { const pre = core.resources(); - await fn(step); + await fn(desc); - if (step.shouldSkipSanitizers) { + if (shouldSkipSanitizers(desc)) { return; } @@ -396,12 +389,12 @@ const hint = resourceCloseHint(postResource); const detail = `${name} (rid ${resource}) was ${action1} during the test, but not ${action2} during the test. ${hint}`; - details.push(detail); + ArrayPrototypePush(details, detail); } else { const [name, action1, action2] = prettyResourceNames(preResource); const detail = `${name} (rid ${resource}) was ${action1} before the test started, but was ${action2} during the test. Do not close resources in a test that were not created during that test.`; - details.push(detail); + ArrayPrototypePush(details, detail); } } @@ -439,79 +432,81 @@ } function assertTestStepScopes(fn) { - /** @param step {TestStep} */ - return async function testStepSanitizer(step) { + /** @param desc {TestDescription | TestStepDescription} */ + return async function testStepSanitizer(desc) { preValidation(); // only report waiting after pre-validation - if (step.canStreamReporting()) { - step.reportWait(); + if (canStreamReporting(desc) && "parent" in desc) { + stepReportWait(desc); } - await fn(createTestContext(step)); - postValidation(); + await fn(MapPrototypeGet(testStates, desc.id).context); + testStepPostValidation(desc); function preValidation() { - const runningSteps = getPotentialConflictingRunningSteps(); - const runningStepsWithSanitizers = ArrayPrototypeFilter( - runningSteps, - (t) => t.usesSanitizer, + const runningStepDescs = getRunningStepDescs(); + const runningStepDescsWithSanitizers = ArrayPrototypeFilter( + runningStepDescs, + (d) => usesSanitizer(d), ); - if (runningStepsWithSanitizers.length > 0) { + if (runningStepDescsWithSanitizers.length > 0) { throw new Error( "Cannot start test step while another test step with sanitizers is running.\n" + - runningStepsWithSanitizers - .map((s) => ` * ${s.getFullName()}`) + runningStepDescsWithSanitizers + .map((d) => ` * ${getFullName(d)}`) .join("\n"), ); } - if (step.usesSanitizer && runningSteps.length > 0) { + if (usesSanitizer(desc) && runningStepDescs.length > 0) { throw new Error( "Cannot start test step with sanitizers while another test step is running.\n" + - runningSteps.map((s) => ` * ${s.getFullName()}`).join("\n"), + runningStepDescs.map((d) => ` * ${getFullName(d)}`).join("\n"), ); } - function getPotentialConflictingRunningSteps() { - /** @type {TestStep[]} */ + function getRunningStepDescs() { const results = []; - - let childStep = step; - for (const ancestor of step.ancestors()) { - for (const siblingStep of ancestor.children) { - if (siblingStep === childStep) { + let childDesc = desc; + while (childDesc.parent != null) { + const state = MapPrototypeGet(testStates, childDesc.parent.id); + for (const siblingDesc of state.children) { + if (siblingDesc.id == childDesc.id) { continue; } - if (!siblingStep.finalized) { - ArrayPrototypePush(results, siblingStep); + const siblingState = MapPrototypeGet(testStates, siblingDesc.id); + if (!siblingState.finalized) { + ArrayPrototypePush(results, siblingDesc); } } - childStep = ancestor; + childDesc = childDesc.parent; } return results; } } + }; + } - function postValidation() { - // check for any running steps - if (step.hasRunningChildren) { - throw new Error( - "There were still test steps running after the current scope finished execution. " + - "Ensure all steps are awaited (ex. `await t.step(...)`).", - ); - } + function testStepPostValidation(desc) { + // check for any running steps + for (const childDesc of MapPrototypeGet(testStates, desc.id).children) { + if (MapPrototypeGet(testStates, childDesc.id).status == "pending") { + throw new Error( + "There were still test steps running after the current scope finished execution. Ensure all steps are awaited (ex. `await t.step(...)`).", + ); + } + } - // check if an ancestor already completed - for (const ancestor of step.ancestors()) { - if (ancestor.finalized) { - throw new Error( - "Parent scope completed before test step finished execution. " + - "Ensure all steps are awaited (ex. `await t.step(...)`).", - ); - } - } + // check if an ancestor already completed + let currentDesc = desc.parent; + while (currentDesc != null) { + if (MapPrototypeGet(testStates, currentDesc.id).finalized) { + throw new Error( + "Parent scope completed before test step finished execution. Ensure all steps are awaited (ex. `await t.step(...)`).", + ); } - }; + currentDesc = currentDesc.parent; + } } function pledgePermissions(permissions) { @@ -541,6 +536,54 @@ * @typedef {{ * id: number, * name: string, + * fn: TestFunction + * origin: string, + * location: TestLocation, + * filteredOut: boolean, + * ignore: boolean, + * only: boolean. + * sanitizeOps: boolean, + * sanitizeResources: boolean, + * sanitizeExit: boolean, + * permissions: PermissionOptions, + * }} TestDescription + * + * @typedef {{ + * id: number, + * name: string, + * fn: TestFunction + * origin: string, + * location: TestLocation, + * ignore: boolean, + * level: number, + * parent: TestDescription | TestStepDescription, + * rootId: number, + * rootName: String, + * sanitizeOps: boolean, + * sanitizeResources: boolean, + * sanitizeExit: boolean, + * }} TestStepDescription + * + * @typedef {{ + * context: TestContext, + * children: TestStepDescription[], + * finalized: boolean, + * }} TestState + * + * @typedef {{ + * context: TestContext, + * children: TestStepDescription[], + * finalized: boolean, + * status: "pending" | "ok" | ""failed" | ignored", + * error: unknown, + * elapsed: number | null, + * reportedWait: boolean, + * reportedResult: boolean, + * }} TestStepState + * + * @typedef {{ + * id: number, + * name: string, * fn: BenchFunction * origin: string, * filteredOut: boolean, @@ -551,7 +594,10 @@ * }} BenchDescription */ - const tests = []; + /** @type {TestDescription[]} */ + const testDescs = []; + /** @type {Map<number, TestState | TestStepState>} */ + const testStates = new Map(); /** @type {BenchDescription[]} */ const benchDescs = []; let isTestOrBenchSubcommand = false; @@ -566,7 +612,7 @@ return; } - let testDef; + let testDesc; const defaults = { ignore: false, only: false, @@ -581,7 +627,7 @@ throw new TypeError("The test name can't be empty"); } if (typeof optionsOrFn === "function") { - testDef = { fn: optionsOrFn, name: nameOrFnOrOptions, ...defaults }; + testDesc = { fn: optionsOrFn, name: nameOrFnOrOptions, ...defaults }; } else { if (!maybeFn || typeof maybeFn !== "function") { throw new TypeError("Missing test function"); @@ -596,7 +642,7 @@ "Unexpected 'name' field in options, test name is already provided as the first argument.", ); } - testDef = { + testDesc = { ...defaults, ...optionsOrFn, fn: maybeFn, @@ -613,7 +659,7 @@ if (maybeFn != undefined) { throw new TypeError("Unexpected third argument to Deno.test()"); } - testDef = { + testDesc = { ...defaults, fn: nameOrFnOrOptions, name: nameOrFnOrOptions.name, @@ -643,29 +689,36 @@ if (!name) { throw new TypeError("The test name can't be empty"); } - testDef = { ...defaults, ...nameOrFnOrOptions, fn, name }; + testDesc = { ...defaults, ...nameOrFnOrOptions, fn, name }; } - testDef.fn = wrapTestFnWithSanitizers(testDef.fn, testDef); - - if (testDef.permissions) { - testDef.fn = withPermissions( - testDef.fn, - testDef.permissions, + // Delete this prop in case the user passed it. It's used to detect steps. + delete testDesc.parent; + testDesc.fn = wrapTestFnWithSanitizers(testDesc.fn, testDesc); + if (testDesc.permissions) { + testDesc.fn = withPermissions( + testDesc.fn, + testDesc.permissions, ); } - + testDesc.origin = getTestOrigin(); const jsError = Deno.core.destructureError(new Error()); - // Note: There might pop up a case where one of the filename, line number or - // column number from the caller isn't defined. We assume never for now. - // Make `TestDescription::location` optional if such a case is found. - testDef.location = { + testDesc.location = { fileName: jsError.frames[1].fileName, lineNumber: jsError.frames[1].lineNumber, columnNumber: jsError.frames[1].columnNumber, }; - ArrayPrototypePush(tests, testDef); + const { id, filteredOut } = core.opSync("op_register_test", testDesc); + testDesc.id = id; + testDesc.filteredOut = filteredOut; + + ArrayPrototypePush(testDescs, testDesc); + MapPrototypeSet(testStates, testDesc.id, { + context: createTestContext(testDesc), + children: [], + finalized: false, + }); } // Main bench function provided by Deno. @@ -769,58 +822,14 @@ ArrayPrototypePush(benchDescs, benchDesc); } - /** - * @param {string | { include?: string[], exclude?: string[] }} filter - * @returns {(def: { name: string }) => boolean} - */ - function createTestFilter(filter) { - if (!filter) { - return () => true; - } - - const regex = - typeof filter === "string" && StringPrototypeStartsWith(filter, "/") && - StringPrototypeEndsWith(filter, "/") - ? new RegExp(StringPrototypeSlice(filter, 1, filter.length - 1)) - : undefined; - - const filterIsObject = filter != null && typeof filter === "object"; - - return (def) => { - if (regex) { - return RegExpPrototypeTest(regex, def.name); - } - if (filterIsObject) { - if (filter.include && !filter.include.includes(def.name)) { - return false; - } else if (filter.exclude && filter.exclude.includes(def.name)) { - return false; - } else { - return true; - } - } - return StringPrototypeIncludes(def.name, filter); - }; - } - - async function runTest(test, description) { - if (test.ignore) { + async function runTest(desc) { + if (desc.ignore) { return "ignored"; } - const step = new TestStep({ - name: test.name, - parent: undefined, - parentContext: undefined, - rootTestDescription: description, - sanitizeOps: test.sanitizeOps, - sanitizeResources: test.sanitizeResources, - sanitizeExit: test.sanitizeExit, - }); - try { - await test.fn(step); - const failCount = step.failedChildStepsCount(); + await desc.fn(desc); + const failCount = failedChildStepsCount(desc); return failCount === 0 ? "ok" : { "failed": core.destructureError( new Error( @@ -833,10 +842,11 @@ "failed": core.destructureError(error), }; } finally { - step.finalized = true; + const state = MapPrototypeGet(testStates, desc.id); + state.finalized = true; // ensure the children report their result - for (const child of step.children) { - child.reportResult(); + for (const childDesc of state.children) { + stepReportResult(childDesc); } } } @@ -961,7 +971,7 @@ n++; avg += iterationTime; - all.push(iterationTime); + ArrayPrototypePush(all, iterationTime); if (iterationTime < min) min = iterationTime; if (iterationTime > max) max = iterationTime; budget -= iterationTime * lowPrecisionThresholdInNs; @@ -1018,36 +1028,6 @@ return origin; } - function reportTestPlan(plan) { - core.opSync("op_dispatch_test_event", { - plan, - }); - } - - function reportTestWait(test) { - core.opSync("op_dispatch_test_event", { - wait: test, - }); - } - - function reportTestResult(test, result, elapsed) { - core.opSync("op_dispatch_test_event", { - result: [test, result, elapsed], - }); - } - - function reportTestStepWait(testDescription) { - core.opSync("op_dispatch_test_event", { - stepWait: testDescription, - }); - } - - function reportTestStepResult(testDescription, result, elapsed) { - core.opSync("op_dispatch_test_event", { - stepResult: [testDescription, result, elapsed], - }); - } - function benchNow() { return core.opSync("op_bench_now"); } @@ -1060,24 +1040,24 @@ } async function runTests({ - filter = null, shuffle = null, } = {}) { core.setMacrotaskCallback(handleOpSanitizerDelayMacrotask); const origin = getTestOrigin(); - - const only = ArrayPrototypeFilter(tests, (test) => test.only); + const only = ArrayPrototypeFilter(testDescs, (test) => test.only); const filtered = ArrayPrototypeFilter( - only.length > 0 ? only : tests, - createTestFilter(filter), + only.length > 0 ? only : testDescs, + (desc) => !desc.filteredOut, ); - reportTestPlan({ - origin, - total: filtered.length, - filteredOut: tests.length - filtered.length, - usedOnly: only.length > 0, + core.opSync("op_dispatch_test_event", { + plan: { + origin, + total: filtered.length, + filteredOut: testDescs.length - filtered.length, + usedOnly: only.length > 0, + }, }); if (shuffle !== null) { @@ -1098,20 +1078,14 @@ } } - for (const test of filtered) { - const description = { - origin, - name: test.name, - location: test.location, - }; + for (const desc of filtered) { + core.opSync("op_dispatch_test_event", { wait: desc.id }); const earlier = DateNow(); - - reportTestWait(description); - - const result = await runTest(test, description); + const result = await runTest(desc); const elapsed = DateNow() - earlier; - - reportTestResult(description, result, elapsed); + core.opSync("op_dispatch_test_event", { + result: [desc.id, result, elapsed], + }); } } @@ -1166,320 +1140,225 @@ globalThis.console = originalConsole; } - /** - * @typedef {{ - * fn: (t: TestContext) => void | Promise<void>, - * name: string, - * ignore?: boolean, - * sanitizeOps?: boolean, - * sanitizeResources?: boolean, - * sanitizeExit?: boolean, - * }} TestStepDefinition - * - * @typedef {{ - * name: string, - * parent: TestStep | undefined, - * parentContext: TestContext | undefined, - * rootTestDescription: { origin: string; name: string }; - * sanitizeOps: boolean, - * sanitizeResources: boolean, - * sanitizeExit: boolean, - * }} TestStepParams - */ - - class TestStep { - /** @type {TestStepParams} */ - #params; - reportedWait = false; - #reportedResult = false; - finalized = false; - elapsed = 0; - /** @type "ok" | "ignored" | "pending" | "failed" */ - status = "pending"; - error = undefined; - /** @type {TestStep[]} */ - children = []; - - /** @param params {TestStepParams} */ - constructor(params) { - this.#params = params; - } - - get name() { - return this.#params.name; - } - - get parent() { - return this.#params.parent; - } - - get parentContext() { - return this.#params.parentContext; - } - - get rootTestDescription() { - return this.#params.rootTestDescription; - } - - get sanitizerOptions() { - return { - sanitizeResources: this.#params.sanitizeResources, - sanitizeOps: this.#params.sanitizeOps, - sanitizeExit: this.#params.sanitizeExit, - }; - } - - get usesSanitizer() { - return this.#params.sanitizeResources || - this.#params.sanitizeOps || - this.#params.sanitizeExit; - } - - /** If a test validation error already occurred then don't bother checking - * the sanitizers as that will create extra noise. - */ - get shouldSkipSanitizers() { - return this.hasRunningChildren || this.parent?.finalized; - } - - get hasRunningChildren() { - return ArrayPrototypeSome( - this.children, - /** @param step {TestStep} */ - (step) => step.status === "pending", - ); - } - - failedChildStepsCount() { - return ArrayPrototypeFilter( - this.children, - /** @param step {TestStep} */ - (step) => step.status === "failed", - ).length; + function getFullName(desc) { + if ("parent" in desc) { + return `${desc.parent.name} > ${desc.name}`; } + return desc.name; + } - canStreamReporting() { - // there should only ever be one sub step running when running with - // sanitizers, so we can use this to tell if we can stream reporting - return this.selfAndAllAncestorsUseSanitizer() && - this.children.every((c) => c.usesSanitizer || c.finalized); - } + function usesSanitizer(desc) { + return desc.sanitizeResources || desc.sanitizeOps || desc.sanitizeExit; + } - selfAndAllAncestorsUseSanitizer() { - if (!this.usesSanitizer) { + function canStreamReporting(desc) { + let currentDesc = desc; + while (currentDesc != null) { + if (!usesSanitizer(currentDesc)) { return false; } - - for (const ancestor of this.ancestors()) { - if (!ancestor.usesSanitizer) { - return false; - } - } - - return true; - } - - *ancestors() { - let ancestor = this.parent; - while (ancestor) { - yield ancestor; - ancestor = ancestor.parent; - } + currentDesc = currentDesc.parent; } - - getFullName() { - if (this.parent) { - return `${this.parent.getFullName()} > ${this.name}`; - } else { - return this.name; + for (const childDesc of MapPrototypeGet(testStates, desc.id).children) { + const state = MapPrototypeGet(testStates, childDesc.id); + if (!usesSanitizer(childDesc) && !state.finalized) { + return false; } } + return true; + } - reportWait() { - if (this.reportedWait || !this.parent) { - return; - } - - reportTestStepWait(this.#getTestStepDescription()); - - this.reportedWait = true; + function stepReportWait(desc) { + const state = MapPrototypeGet(testStates, desc.id); + if (state.reportedWait) { + return; } + core.opSync("op_dispatch_test_event", { stepWait: desc.id }); + state.reportedWait = true; + } - reportResult() { - if (this.#reportedResult || !this.parent) { - return; - } - - this.reportWait(); - - for (const child of this.children) { - child.reportResult(); - } - - reportTestStepResult( - this.#getTestStepDescription(), - this.#getStepResult(), - this.elapsed, - ); - - this.#reportedResult = true; + function stepReportResult(desc) { + const state = MapPrototypeGet(testStates, desc.id); + if (state.reportedResult) { + return; } - - #getStepResult() { - switch (this.status) { - case "ok": - return "ok"; - case "ignored": - return "ignored"; - case "pending": - return { - "pending": this.error && core.destructureError(this.error), - }; - case "failed": - return { - "failed": this.error && core.destructureError(this.error), - }; - default: - throw new Error(`Unhandled status: ${this.status}`); - } + stepReportWait(desc); + for (const childDesc of state.children) { + stepReportResult(childDesc); } - - #getTestStepDescription() { - return { - test: this.rootTestDescription, - name: this.name, - level: this.#getLevel(), + let result; + if (state.status == "pending" || state.status == "failed") { + result = { + [state.status]: state.error && core.destructureError(state.error), }; + } else { + result = state.status; } + core.opSync("op_dispatch_test_event", { + stepResult: [desc.id, result, state.elapsed], + }); + state.reportedResult = true; + } - #getLevel() { - let count = 0; - for (const _ of this.ancestors()) { - count++; - } - return count; + function failedChildStepsCount(desc) { + return ArrayPrototypeFilter( + MapPrototypeGet(testStates, desc.id).children, + (d) => MapPrototypeGet(testStates, d.id).status === "failed", + ).length; + } + + /** If a test validation error already occurred then don't bother checking + * the sanitizers as that will create extra noise. + */ + function shouldSkipSanitizers(desc) { + try { + testStepPostValidation(desc); + return false; + } catch { + return true; } } - /** @param parentStep {TestStep} */ - function createTestContext(parentStep) { + /** @param desc {TestDescription | TestStepDescription} */ + function createTestContext(desc) { + let parent; + let level; + let rootId; + let rootName; + if ("parent" in desc) { + parent = MapPrototypeGet(testStates, desc.parent.id).context; + level = desc.level; + rootId = desc.rootId; + rootName = desc.rootName; + } else { + parent = undefined; + level = 0; + rootId = desc.id; + rootName = desc.name; + } return { [SymbolToStringTag]: "TestContext", /** * The current test name. */ - name: parentStep.name, + name: desc.name, /** * Parent test context. */ - parent: parentStep.parentContext ?? undefined, + parent, /** * File Uri of the test code. */ - origin: parentStep.rootTestDescription.origin, + origin: desc.origin, /** * @param nameOrTestDefinition {string | TestStepDefinition} * @param fn {(t: TestContext) => void | Promise<void>} */ async step(nameOrTestDefinition, fn) { - if (parentStep.finalized) { + if (MapPrototypeGet(testStates, desc.id).finalized) { throw new Error( "Cannot run test step after parent scope has finished execution. " + "Ensure any `.step(...)` calls are executed before their parent scope completes execution.", ); } - const definition = getDefinition(); - const subStep = new TestStep({ - name: definition.name, - parent: parentStep, - parentContext: this, - rootTestDescription: parentStep.rootTestDescription, - sanitizeOps: getOrDefault( - definition.sanitizeOps, - parentStep.sanitizerOptions.sanitizeOps, - ), - sanitizeResources: getOrDefault( - definition.sanitizeResources, - parentStep.sanitizerOptions.sanitizeResources, - ), - sanitizeExit: getOrDefault( - definition.sanitizeExit, - parentStep.sanitizerOptions.sanitizeExit, - ), - }); - - ArrayPrototypePush(parentStep.children, subStep); + let stepDesc; + if (typeof nameOrTestDefinition === "string") { + if (!(ObjectPrototypeIsPrototypeOf(FunctionPrototype, fn))) { + throw new TypeError("Expected function for second argument."); + } + stepDesc = { + name: nameOrTestDefinition, + fn, + }; + } else if (typeof nameOrTestDefinition === "object") { + stepDesc = nameOrTestDefinition; + } else { + throw new TypeError( + "Expected a test definition or name and function.", + ); + } + stepDesc.ignore ??= false; + stepDesc.sanitizeOps ??= desc.sanitizeOps; + stepDesc.sanitizeResources ??= desc.sanitizeResources; + stepDesc.sanitizeExit ??= desc.sanitizeExit; + stepDesc.origin = getTestOrigin(); + const jsError = Deno.core.destructureError(new Error()); + stepDesc.location = { + fileName: jsError.frames[1].fileName, + lineNumber: jsError.frames[1].lineNumber, + columnNumber: jsError.frames[1].columnNumber, + }; + stepDesc.level = level + 1; + stepDesc.parent = desc; + stepDesc.rootId = rootId; + stepDesc.rootName = rootName; + const { id } = core.opSync("op_register_test_step", stepDesc); + stepDesc.id = id; + const state = { + context: createTestContext(stepDesc), + children: [], + finalized: false, + status: "pending", + error: null, + elapsed: null, + reportedWait: false, + reportedResult: false, + }; + MapPrototypeSet(testStates, stepDesc.id, state); + ArrayPrototypePush( + MapPrototypeGet(testStates, stepDesc.parent.id).children, + stepDesc, + ); try { - if (definition.ignore) { - subStep.status = "ignored"; - subStep.finalized = true; - if (subStep.canStreamReporting()) { - subStep.reportResult(); + if (stepDesc.ignore) { + state.status = "ignored"; + state.finalized = true; + if (canStreamReporting(stepDesc)) { + stepReportResult(stepDesc); } return false; } - const testFn = wrapTestFnWithSanitizers( - definition.fn, - subStep.sanitizerOptions, - ); + const testFn = wrapTestFnWithSanitizers(stepDesc.fn, stepDesc); const start = DateNow(); try { - await testFn(subStep); + await testFn(stepDesc); - if (subStep.failedChildStepsCount() > 0) { - subStep.status = "failed"; + if (failedChildStepsCount(stepDesc) > 0) { + state.status = "failed"; } else { - subStep.status = "ok"; + state.status = "ok"; } } catch (error) { - subStep.error = error; - subStep.status = "failed"; + state.error = error; + state.status = "failed"; } - subStep.elapsed = DateNow() - start; + state.elapsed = DateNow() - start; - if (subStep.parent?.finalized) { + if (MapPrototypeGet(testStates, stepDesc.parent.id).finalized) { // always point this test out as one that was still running // if the parent step finalized - subStep.status = "pending"; + state.status = "pending"; } - subStep.finalized = true; + state.finalized = true; - if (subStep.reportedWait && subStep.canStreamReporting()) { - subStep.reportResult(); + if (state.reportedWait && canStreamReporting(stepDesc)) { + stepReportResult(stepDesc); } - return subStep.status === "ok"; + return state.status === "ok"; } finally { - if (parentStep.canStreamReporting()) { + if (canStreamReporting(stepDesc.parent)) { + const parentState = MapPrototypeGet(testStates, stepDesc.parent.id); // flush any buffered steps - for (const parentChild of parentStep.children) { - parentChild.reportResult(); - } - } - } - - /** @returns {TestStepDefinition} */ - function getDefinition() { - if (typeof nameOrTestDefinition === "string") { - if (!(ObjectPrototypeIsPrototypeOf(FunctionPrototype, fn))) { - throw new TypeError("Expected function for second argument."); + for (const childDesc of parentState.children) { + stepReportResult(childDesc); } - return { - name: nameOrTestDefinition, - fn, - }; - } else if (typeof nameOrTestDefinition === "object") { - return nameOrTestDefinition; - } else { - throw new TypeError( - "Expected a test definition or name and function.", - ); } } }, @@ -1511,16 +1390,6 @@ return testFn; } - /** - * @template T - * @param value {T | undefined} - * @param defaultValue {T} - * @returns T - */ - function getOrDefault(value, defaultValue) { - return value == null ? defaultValue : value; - } - window.__bootstrap.internals = { ...window.__bootstrap.internals ?? {}, enableTestAndBench, |