Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(runner): add "queued" state #6931

Merged
merged 18 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/browser/src/client/tester/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ export function createBrowserRunner(
}
}

onCollectStart = (file: File) => {
return rpc().onQueued(file)
}

onCollected = async (files: File[]): Promise<unknown> => {
files.forEach((file) => {
file.prepareDuration = state.durations.prepare
Expand Down
4 changes: 4 additions & 0 deletions packages/browser/src/node/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ export function setupBrowserRpc(server: BrowserServer) {
}
ctx.state.catchError(error, type)
},
async onQueued(file) {
ctx.state.collectFiles(project, [file])
await ctx.report('onQueued', file)
},
async onCollected(files) {
ctx.state.collectFiles(project, files)
await ctx.report('onCollected', files)
Expand Down
1 change: 1 addition & 0 deletions packages/browser/src/node/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface WebSocketBrowserHandlers {
resolveSnapshotPath: (testPath: string) => string
resolveSnapshotRawPath: (testPath: string, rawPath: string) => string
onUnhandledError: (error: unknown, type: string) => Promise<void>
onQueued: (file: RunnerTestFile) => void
onCollected: (files?: RunnerTestFile[]) => Promise<void>
onTaskUpdate: (packs: TaskResultPack[]) => void
onAfterSuiteRun: (meta: AfterSuiteRunMeta) => void
Expand Down
4 changes: 4 additions & 0 deletions packages/runner/src/collect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ export async function collectTests(
config.allowOnly,
)

if (file.mode === 'queued') {
file.mode = 'run'
}

files.push(file)
}

Expand Down
4 changes: 2 additions & 2 deletions packages/runner/src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ async function callCleanupHooks(cleanups: HookCleanupCallback[]) {
export async function runTest(test: Test, runner: VitestRunner): Promise<void> {
await runner.onBeforeRunTask?.(test)

if (test.mode !== 'run') {
if (test.mode !== 'run' && test.mode !== 'queued') {
return
}

Expand Down Expand Up @@ -458,7 +458,7 @@ export async function runSuite(suite: Suite, runner: VitestRunner): Promise<void
failTask(suite.result, e, runner.config.diffOptions)
}

if (suite.mode === 'run') {
if (suite.mode === 'run' || suite.mode === 'queued') {
if (!runner.config.passWithNoTests && !hasTests(suite)) {
suite.result.state = 'fail'
if (!suite.result.errors?.length) {
Expand Down
3 changes: 2 additions & 1 deletion packages/runner/src/types/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Awaitable, ErrorWithDiff } from '@vitest/utils'
import type { FixtureItem } from '../fixture'
import type { ChainableFunction } from '../utils/chain'

export type RunMode = 'run' | 'skip' | 'only' | 'todo'
export type RunMode = 'run' | 'skip' | 'only' | 'todo' | 'queued'
export type TaskState = RunMode | 'pass' | 'fail'

export interface TaskBase {
Expand All @@ -23,6 +23,7 @@ export interface TaskBase {
* - **only**: only this task and other tasks with `only` mode will run
* - **todo**: task is marked as a todo, alias for `skip`
* - **run**: task will run or already ran
* - **queued**: task will start running next. It can only exist on the File
*/
mode: RunMode
/**
Expand Down
8 changes: 4 additions & 4 deletions packages/runner/src/utils/collect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ export function interpretTaskModes(
})

// if all subtasks are skipped, mark as skip
if (suite.mode === 'run') {
if (suite.tasks.length && suite.tasks.every(i => i.mode !== 'run')) {
if (suite.mode === 'run' || suite.mode === 'queued') {
if (suite.tasks.length && suite.tasks.every(i => i.mode !== 'run' && i.mode !== 'queued')) {
suite.mode = 'skip'
}
}
Expand Down Expand Up @@ -115,7 +115,7 @@ export function someTasksAreOnly(suite: Suite): boolean {

function skipAllTasks(suite: Suite) {
suite.tasks.forEach((t) => {
if (t.mode === 'run') {
if (t.mode === 'run' || t.mode === 'queued') {
t.mode = 'skip'
if (t.type === 'suite') {
skipAllTasks(t)
Expand Down Expand Up @@ -172,7 +172,7 @@ export function createFileTask(
id: generateHash(`${path}${projectName || ''}`),
name: path,
type: 'suite',
mode: 'run',
mode: 'queued',
filepath,
tasks: [],
meta: Object.create(null),
Expand Down
4 changes: 2 additions & 2 deletions packages/vitest/src/node/cli/cli-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ function forEachSuite(tasks: Task[], callback: (suite: Suite) => void) {

export function formatCollectedAsJSON(files: File[]) {
return files.map((file) => {
const tests = getTests(file).filter(test => test.mode === 'run' || test.mode === 'only')
const tests = getTests(file).filter(test => test.mode === 'run' || test.mode === 'queued' || test.mode === 'only')
return tests.map((test) => {
const result: any = {
name: getNames(test).slice(1).join(' > '),
Expand All @@ -276,7 +276,7 @@ export function formatCollectedAsJSON(files: File[]) {

export function formatCollectedAsString(files: File[]) {
return files.map((file) => {
const tests = getTests(file).filter(test => test.mode === 'run' || test.mode === 'only')
const tests = getTests(file).filter(test => test.mode === 'run' || test.mode === 'queued' || test.mode === 'only')
return tests.map((test) => {
const name = getNames(test).join(' > ')
if (test.file.projectName) {
Expand Down
10 changes: 9 additions & 1 deletion packages/vitest/src/node/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,15 @@ export class Vitest {
return { tests: [], errors: [] }
}

await this.collectFiles(files)
const reporters = this.reporters
this.reporters = []

try {
await this.collectFiles(files)
}
finally {
this.reporters = reporters
}

return {
tests: this.state.getFiles(),
Expand Down
4 changes: 4 additions & 0 deletions packages/vitest/src/node/pools/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ export function createMethodsRPC(project: TestProject, options: MethodsOptions =
ctx.state.collectPaths(paths)
return ctx.report('onPathsCollected', paths)
},
onQueued(file) {
ctx.state.collectFiles(project, [file])
return ctx.report('onQueued', file)
},
onCollected(files) {
ctx.state.collectFiles(project, files)
return ctx.report('onCollected', files)
Expand Down
3 changes: 2 additions & 1 deletion packages/vitest/src/node/reporters/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ export abstract class BaseReporter implements Reporter {
if (
!('filepath' in task)
|| !task.result?.state
|| task.result?.state === 'run') {
|| task.result?.state === 'run'
|| task.result?.state === 'queued') {
return
}

Expand Down
3 changes: 2 additions & 1 deletion packages/vitest/src/node/reporters/benchmark/table/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,13 @@ export class TableReporter extends BaseReporter {
&& task.type === 'suite'
&& task.result?.state
&& task.result?.state !== 'run'
&& task.result?.state !== 'queued'
) {
// render static table when all benches inside single suite are finished
const benches = task.tasks.filter(t => t.meta.benchmark)
if (
benches.length > 0
&& benches.every(t => t.result?.state !== 'run')
&& benches.every(t => t.result?.state !== 'run' && t.result?.state !== 'queued')
) {
let title = ` ${getStateSymbol(task)} ${getFullName(
task,
Expand Down
4 changes: 4 additions & 0 deletions packages/vitest/src/node/reporters/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export class DefaultReporter extends BaseReporter {
}
}

onQueued(file: File) {
this.summary?.onQueued(file)
}

onInit(ctx: Vitest) {
super.onInit(ctx)
this.summary?.onInit(ctx, { verbose: this.verbose })
Expand Down
7 changes: 4 additions & 3 deletions packages/vitest/src/node/reporters/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const StatusMap: Record<TaskState, Status> = {
run: 'pending',
skip: 'skipped',
todo: 'todo',
queued: 'pending',
}

export interface JsonAssertionResult {
Expand Down Expand Up @@ -95,7 +96,7 @@ export class JsonReporter implements Reporter {

const numFailedTestSuites = suites.filter(s => s.result?.state === 'fail').length
const numPendingTestSuites = suites.filter(
s => s.result?.state === 'run' || s.mode === 'todo',
s => s.result?.state === 'run' || s.result?.state === 'queued' || s.mode === 'todo',
).length
const numPassedTestSuites = numTotalTestSuites - numFailedTestSuites - numPendingTestSuites

Expand All @@ -104,7 +105,7 @@ export class JsonReporter implements Reporter {
).length
const numPassedTests = tests.filter(t => t.result?.state === 'pass').length
const numPendingTests = tests.filter(
t => t.result?.state === 'run' || t.mode === 'skip' || t.result?.state === 'skip',
t => t.result?.state === 'run' || t.result?.state === 'queued' || t.mode === 'skip' || t.result?.state === 'skip',
).length
const numTodoTests = tests.filter(t => t.mode === 'todo').length
const testResults: Array<JsonTestResult> = []
Expand Down Expand Up @@ -154,7 +155,7 @@ export class JsonReporter implements Reporter {
} satisfies JsonAssertionResult
})

if (tests.some(t => t.result?.state === 'run')) {
if (tests.some(t => t.result?.state === 'run' || t.result?.state === 'queued')) {
this.ctx.logger.warn(
'WARNING: Some tests are still running when generating the JSON report.'
+ 'This is likely an internal bug in Vitest.'
Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/src/node/reporters/renderers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export function getStateSymbol(task: Task) {
return pending
}

if (task.result.state === 'run') {
if (task.result.state === 'run' || task.result.state === 'queued') {
if (task.type === 'suite') {
return pointer
}
Expand Down
6 changes: 3 additions & 3 deletions packages/vitest/src/node/reporters/reported-tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export class TestCase extends ReportedTaskImplementation {
*/
public result(): TestResult | undefined {
const result = this.task.result
if (!result || result.state === 'run') {
if (!result || result.state === 'run' || result.state === 'queued') {
return undefined
}
const state = result.state === 'fail'
Expand Down Expand Up @@ -163,7 +163,7 @@ export class TestCase extends ReportedTaskImplementation {
public diagnostic(): TestDiagnostic | undefined {
const result = this.task.result
// startTime should always be available if the test has properly finished
if (!result || result.state === 'run' || !result.startTime) {
if (!result || result.state === 'run' || result.state === 'queued' || !result.startTime) {
return undefined
}
const duration = result.duration || 0
Expand Down Expand Up @@ -400,7 +400,7 @@ export interface TaskOptions {
shuffle: boolean | undefined
retry: number | undefined
repeats: number | undefined
mode: 'run' | 'only' | 'skip' | 'todo'
mode: 'run' | 'only' | 'skip' | 'todo' | 'queued'
}

function buildOptions(
Expand Down
4 changes: 4 additions & 0 deletions packages/vitest/src/node/reporters/summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ export class SummaryReporter extends TaskParser implements Reporter {
})
}

onQueued(file: File) {
this.onTestFilePrepare(file)
}

onPathsCollected(paths?: string[]) {
this.suites.total = (paths || []).length
}
Expand Down
9 changes: 4 additions & 5 deletions packages/vitest/src/node/reporters/task-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class TaskParser {
const task = this.ctx.state.idMap.get(pack[0])

if (task?.type === 'suite' && 'filepath' in task && task.result?.state) {
if (task?.result?.state === 'run') {
if (task?.result?.state === 'run' || task?.result?.state === 'queued') {
startingTestFiles.push(task)
}
else {
Expand All @@ -55,7 +55,7 @@ export class TaskParser {
}

if (task?.type === 'test') {
if (task.result?.state === 'run') {
if (task.result?.state === 'run' || task.result?.state === 'queued') {
startingTests.push(task)
}
else if (task.result?.hooks?.afterEach !== 'run') {
Expand All @@ -65,7 +65,7 @@ export class TaskParser {

if (task?.result?.hooks) {
for (const [hook, state] of Object.entries(task.result.hooks)) {
if (state === 'run') {
if (state === 'run' || state === 'queued') {
startingHooks.push({ name: hook, file: task.file, id: task.id, type: task.type })
}
else {
Expand All @@ -81,7 +81,6 @@ export class TaskParser {

startingTestFiles.forEach(file => this.onTestFilePrepare(file))
startingTests.forEach(test => this.onTestStart(test))
startingHooks.forEach(hook => this.onHookStart(hook),
)
startingHooks.forEach(hook => this.onHookStart(hook))
}
}
1 change: 1 addition & 0 deletions packages/vitest/src/node/reporters/verbose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class VerboseReporter extends DefaultReporter {
&& task.type === 'test'
&& task.result?.state
&& task.result?.state !== 'run'
&& task.result?.state !== 'queued'
) {
let title = ` ${getStateSymbol(task)} `
if (task.file.projectName) {
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/node/types/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface Reporter {
onInit?: (ctx: Vitest) => void
onPathsCollected?: (paths?: string[]) => Awaitable<void>
onSpecsCollected?: (specs?: SerializedTestSpecification[]) => Awaitable<void>
onQueued?: (file: File) => Awaitable<void>
onCollected?: (files?: File[]) => Awaitable<void>
onFinished?: (
files: File[],
Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/src/runtime/runners/benchmark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async function runBenchmarkSuite(suite: Suite, runner: NodeBenchmarkRunner) {
const benchmarkGroup: Benchmark[] = []
const benchmarkSuiteGroup = []
for (const task of suite.tasks) {
if (task.mode !== 'run') {
if (task.mode !== 'run' && task.mode !== 'queued') {
continue
}

Expand Down
6 changes: 6 additions & 0 deletions packages/vitest/src/runtime/runners/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ export async function resolveTestRunner(
return p
}

const originalOnCollectStart = testRunner.onCollectStart
testRunner.onCollectStart = async (file) => {
await rpc().onQueued(file)
await originalOnCollectStart?.call(testRunner, file)
}

const originalOnCollected = testRunner.onCollected
testRunner.onCollected = async (files) => {
const state = getWorkerState()
Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/src/runtime/runners/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export class VitestTestRunner implements VitestRunner {
test.mode = 'skip'
}

if (test.mode !== 'run') {
if (test.mode !== 'run' && test.mode !== 'queued') {
return
}

Expand Down
4 changes: 2 additions & 2 deletions packages/vitest/src/typecheck/collect.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { File, Suite, Test } from '@vitest/runner'
import type { File, RunMode, Suite, Test } from '@vitest/runner'
import type { Node } from 'estree'
import type { RawSourceMap } from 'vite-node'
import type { TestProject } from '../node/project'
Expand Down Expand Up @@ -32,7 +32,7 @@ interface LocalCallDefinition {
end: number
name: string
type: 'suite' | 'test'
mode: 'run' | 'skip' | 'only' | 'todo'
mode: RunMode
task: ParsedSuite | ParsedFile | ParsedTest
}

Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/src/typecheck/typechecker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export class Typechecker {
if ('tasks' in task) {
markTasks(task.tasks)
}
if (!task.result?.state && task.mode === 'run') {
if (!task.result?.state && (task.mode === 'run' || task.mode === 'queued')) {
task.result = {
state: 'pass',
}
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/types/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export interface RuntimeRPC {
onPathsCollected: (paths: string[]) => void
onUserConsoleLog: (log: UserConsoleLog) => void
onUnhandledError: (err: unknown, type: string) => void
onQueued: (file: File) => void
onCollected: (files: File[]) => Promise<void>
onAfterSuiteRun: (meta: AfterSuiteRunMeta) => void
onTaskUpdate: (pack: TaskResultPack[]) => Promise<void>
Expand Down
5 changes: 5 additions & 0 deletions test/reporters/fixtures/long-loading-task.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { test } from 'vitest'

await new Promise(r => setTimeout(r, 500))

test('works')
Loading
Loading