diff --git a/src/reporters/default.ts b/src/reporters/default.ts index 41490d153..5227595fc 100644 --- a/src/reporters/default.ts +++ b/src/reporters/default.ts @@ -83,8 +83,8 @@ export class DefaultReporter implements Reporter { return { title: this.relative(file.filepath), task: () => { - if (file.error) - throw file.error + if (file.result?.error) + throw file.result?.error return createSuiteListr(file) }, @@ -116,8 +116,8 @@ export class DefaultReporter implements Reporter { const suites = files.flatMap(file => file.children.filter(c => c.type === 'suite')) as Suite[] const tasks = files.flatMap(getSuiteTasks) - const failedFiles = files.filter(i => i.error) - const failedSuites = suites.filter(i => i.error) + const failedFiles = files.filter(i => i.result?.error) + const failedSuites = suites.filter(i => i.result?.error) const runnable = tasks.filter(i => i.result?.state === 'pass' || i.result?.state === 'fail') const passed = tasks.filter(i => i.result?.state === 'pass') @@ -132,7 +132,7 @@ export class DefaultReporter implements Reporter { console.log() for (const file of failedFiles) { - await printError(file.error) + await printError(file.result?.error) console.log() } } @@ -141,7 +141,7 @@ export class DefaultReporter implements Reporter { console.error(c.bold(c.red(`\nFailed to run ${failedSuites.length} suites:`))) for (const suite of failedSuites) { console.error(c.red(`\n- ${suite.file?.filepath} > ${suite.name}`)) - await printError(suite.error) + await printError(suite.result?.error) console.log() } } diff --git a/src/runtime/collect.ts b/src/runtime/collect.ts index 5f396025c..cfb249402 100644 --- a/src/runtime/collect.ts +++ b/src/runtime/collect.ts @@ -1,6 +1,6 @@ import { File, Suite, Task } from '../types' +import { interpretOnlyMode } from '../utils' import { clearContext, createSuiteHooks, defaultSuite } from './suite' -import { interpretOnlyMode } from './run' import { context } from './context' import { setHooks } from './map' @@ -35,7 +35,11 @@ export async function collectTests(paths: string[]) { } } catch (e) { - file.error = e + file.result = { + start: performance.now(), + state: 'fail', + error: e, + } process.exitCode = 1 } diff --git a/src/runtime/run.ts b/src/runtime/run.ts index 8a2d63c66..3b75f367d 100644 --- a/src/runtime/run.ts +++ b/src/runtime/run.ts @@ -1,8 +1,9 @@ import { HookListener } from 'vitest' -import { File, ResolvedConfig, Task, RunnerContext, Suite, RunMode, SuiteHooks } from '../types' +import { File, ResolvedConfig, Task, RunnerContext, Suite, SuiteHooks, TaskOrSuite } from '../types' import { getSnapshotManager } from '../integrations/chai/snapshot' import { startWatcher } from '../node/watcher' import { globTestFiles } from '../node/glob' +import { partitionSuiteChildren } from '../utils' import { getFn, getHooks } from './map' import { collectTests } from './collect' import { setupRunner } from './setup' @@ -49,30 +50,21 @@ export async function runTask(task: Task, ctx: RunnerContext) { await reporter.onTaskEnd?.(task, ctx) } -/** - * If any items been marked as `only`, mark all other items as `skip`. - */ -export function interpretOnlyMode(items: { mode: RunMode }[]) { - if (items.some(i => i.mode === 'only')) { - items.forEach((i) => { - if (i.mode === 'run') - i.mode = 'skip' - else if (i.mode === 'only') - i.mode = 'run' - }) - } -} - export async function runSuite(suite: Suite, ctx: RunnerContext) { const { reporter } = ctx await reporter.onSuiteBegin?.(suite, ctx) + suite.result = { + start: performance.now(), + state: 'run', + } + if (suite.mode === 'skip') { - suite.status = 'skip' + suite.result.state = 'skip' } else if (suite.mode === 'todo') { - suite.status = 'todo' + suite.result.state = 'todo' } else { try { @@ -92,37 +84,20 @@ export async function runSuite(suite: Suite, ctx: RunnerContext) { await callHook(suite, 'afterAll', [suite]) } catch (e) { - suite.error = e - suite.status = 'fail' + suite.result.error = e + suite.result.state = 'fail' process.exitCode = 1 } } + suite.result.end = performance.now() + await reporter.onSuiteEnd?.(suite, ctx) } -async function runSuiteChild(c: (Task | Suite), ctx: RunnerContext) { - return c.type === 'task' ? runTask(c, ctx) : runSuite(c, ctx) -} - -/** - * Partition in tasks groups by consecutive computeMode ('serial', 'concurrent') - */ -function partitionSuiteChildren(suite: Suite) { - let childrenGroup: (Task | Suite)[] = [] - const childrenGroups: (Task | Suite)[][] = [] - for (const c of suite.children) { - if (childrenGroup.length === 0 || c.computeMode === childrenGroup[0].computeMode) { - childrenGroup.push(c) - } - else { - childrenGroups.push(childrenGroup) - childrenGroup = [c] - } - } - if (childrenGroup.length > 0) - childrenGroups.push(childrenGroup) - - return childrenGroups +async function runSuiteChild(c: TaskOrSuite, ctx: RunnerContext) { + return c.type === 'task' + ? runTask(c, ctx) + : runSuite(c, ctx) } export async function runFile(file: File, ctx: RunnerContext) { diff --git a/src/types.ts b/src/types.ts index 39d9d616c..84548953d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -103,6 +103,8 @@ export interface TaskResult { error?: unknown } +export type TaskOrSuite = Task | Suite + export type TestFunction = () => Awaitable interface ConcurrentCollector { @@ -149,11 +151,9 @@ export interface Suite { name: string mode: RunMode computeMode: ComputeMode - children: (Task | Suite)[] - state?: SuiteState + children: TaskOrSuite[] file?: File - error?: unknown - status?: TaskState + result?: TaskResult } export interface SuiteCollector { @@ -171,7 +171,6 @@ export type TestFactory = (test: (name: string, fn: TestFunction) => void) => Aw export interface File extends Suite { filepath: string - error?: unknown } export interface RunnerContext { diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 000000000..dff4d9818 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,36 @@ +import { RunMode, Suite, TaskOrSuite } from './types' + +/** + * Partition in tasks groups by consecutive computeMode ('serial', 'concurrent') + */ +export function partitionSuiteChildren(suite: Suite) { + let childrenGroup: TaskOrSuite[] = [] + const childrenGroups: TaskOrSuite[][] = [] + for (const c of suite.children) { + if (childrenGroup.length === 0 || c.computeMode === childrenGroup[0].computeMode) { + childrenGroup.push(c) + } + else { + childrenGroups.push(childrenGroup) + childrenGroup = [c] + } + } + if (childrenGroup.length > 0) + childrenGroups.push(childrenGroup) + + return childrenGroups +} + +/** + * If any items been marked as `only`, mark all other items as `skip`. + */ +export function interpretOnlyMode(items: { mode: RunMode }[]) { + if (items.some(i => i.mode === 'only')) { + items.forEach((i) => { + if (i.mode === 'run') + i.mode = 'skip' + else if (i.mode === 'only') + i.mode = 'run' + }) + } +}