Skip to content

Commit

Permalink
refactor: move onTaskUpdate to its own TaskParser
Browse files Browse the repository at this point in the history
  • Loading branch information
AriPerkkio committed Nov 14, 2024
1 parent e39d1f9 commit b7a381c
Show file tree
Hide file tree
Showing 7 changed files with 370 additions and 110 deletions.
153 changes: 43 additions & 110 deletions packages/vitest/src/node/reporters/summary.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import type { Custom, File, Task, TaskResultPack, Test } from '@vitest/runner'
import type { Custom, File, Test } from '@vitest/runner'
import type { Vitest } from '../core'
import type { Reporter } from '../types/reporter'
import type { HookOptions } from './task-parser'
import { getTests } from '@vitest/runner/utils'
import c from 'tinyrainbow'
import { F_POINTER, F_TREE_NODE_END, F_TREE_NODE_MIDDLE } from './renderers/figures'
import { formatProjectName, formatTime, formatTimeString, padSummaryTitle } from './renderers/utils'
import { WindowRenderer } from './renderers/windowedRenderer'
import { TaskParser } from './task-parser'

const DURATION_UPDATE_INTERVAL_MS = 100
const FINISHED_TEST_CLEANUP_TIME_MS = 1_000
Expand All @@ -23,14 +25,6 @@ interface Counter {
todo: number
}

interface HookOptions {
name: string
file: File
id: File['id'] | Test['id']
type: Task['type']

}

interface SlowTask {
name: string
visible: boolean
Expand All @@ -50,8 +44,7 @@ interface RunningTest extends Pick<Counter, 'total' | 'completed'> {
* Reporter extension that renders summary and forwards all other logs above itself.
* Intended to be used by other reporters, not as a standalone reporter.
*/
export class SummaryReporter implements Reporter {
private ctx!: Vitest
export class SummaryReporter extends TaskParser implements Reporter {
private options!: Options
private renderer!: WindowRenderer

Expand Down Expand Up @@ -98,66 +91,6 @@ export class SummaryReporter implements Reporter {
this.suites.total = (paths || []).length
}

onTaskUpdate(packs: TaskResultPack[]) {
const startingTestFiles: File[] = []
const finishedTestFiles: File[] = []

const startingTests: (Test | Custom)[] = []
const finishedTests: (Test | Custom)[] = []

const startingHooks: HookOptions[] = []
const endingHooks: HookOptions[] = []

for (const pack of packs) {
const task = this.ctx.state.idMap.get(pack[0])

if (task?.type === 'suite' && 'filepath' in task && task.result?.state) {
if (task?.result?.state === 'run') {
startingTestFiles.push(task)
}
else {
// Skipped tests are not reported, do it manually
for (const test of getTests(task)) {
if (!test.result || test.result?.state === 'skip') {
finishedTests.push(test)
}
}

finishedTestFiles.push(task.file)
}
}

if (task?.type === 'test' || task?.type === 'custom') {
if (task.result?.state === 'run') {
startingTests.push(task)
}
else if (task.result?.hooks?.afterEach !== 'run') {
finishedTests.push(task)
}
}

if (task?.result?.hooks) {
for (const [hook, state] of Object.entries(task.result.hooks)) {
if (state === 'run') {
startingHooks.push({ name: hook, file: task.file, id: task.id, type: task.type })
}
else {
endingHooks.push({ name: hook, file: task.file, id: task.id, type: task.type })
}
}
}
}

endingHooks.forEach(hook => this.onHookEnd(hook))
finishedTests.forEach(test => this.onTestFinished(test))
finishedTestFiles.forEach(file => this.onTestFileFinished(file))

startingTestFiles.forEach(file => this.onTestFilePrepare(file))
startingTests.forEach(test => this.onTestStart(test))
startingHooks.forEach(hook => this.onHookStart(hook),
)
}

onWatcherRerun() {
this.runningTests.clear()
this.finishedTests.clear()
Expand All @@ -177,7 +110,7 @@ export class SummaryReporter implements Reporter {
clearInterval(this.durationInterval)
}

private onTestFilePrepare(file: File) {
onTestFilePrepare(file: File) {
if (this.allFinishedTests.has(file.id) || this.runningTests.has(file.id)) {
return
}
Expand All @@ -202,40 +135,7 @@ export class SummaryReporter implements Reporter {
this.maxParallelTests = Math.max(this.maxParallelTests, this.runningTests.size)
}

private getTestStats(test: Test | Custom) {
const file = test.file
let stats = this.runningTests.get(file.id)

if (!stats) {
// It's possible that that test finished before it's preparation was even reported
this.onTestFilePrepare(test.file)
stats = this.runningTests.get(file.id)!

// It's also possible that this update came after whole test file was reported as finished
if (!stats) {
return
}
}

return stats
}

private getHookStats({ file, id, type }: HookOptions) {
// Track slow running hooks only on verbose mode
if (!this.options.verbose) {
return
}

const stats = this.runningTests.get(file.id)

if (!stats) {
return
}

return type === 'suite' ? stats : stats?.tests.get(id)
}

private onHookStart(options: HookOptions) {
onHookStart(options: HookOptions) {
const stats = this.getHookStats(options)

if (!stats) {
Expand All @@ -258,7 +158,7 @@ export class SummaryReporter implements Reporter {
hook.onFinish = () => clearTimeout(timeout)
}

private onHookEnd(options: HookOptions) {
onHookEnd(options: HookOptions) {
const stats = this.getHookStats(options)

if (stats?.hook?.name !== options.name) {
Expand All @@ -269,7 +169,7 @@ export class SummaryReporter implements Reporter {
stats.hook.visible = false
}

private onTestStart(test: Test | Custom) {
onTestStart(test: Test | Custom) {
// Track slow running tests only on verbose mode
if (!this.options.verbose) {
return
Expand Down Expand Up @@ -300,7 +200,7 @@ export class SummaryReporter implements Reporter {
stats.tests.set(test.id, slowTest)
}

private onTestFinished(test: Test | Custom) {
onTestFinished(test: Test | Custom) {
const stats = this.getTestStats(test)

if (!stats) {
Expand All @@ -324,7 +224,7 @@ export class SummaryReporter implements Reporter {
}
}

private onTestFileFinished(file: File) {
onTestFileFinished(file: File) {
if (this.allFinishedTests.has(file.id)) {
return
}
Expand Down Expand Up @@ -362,6 +262,39 @@ export class SummaryReporter implements Reporter {
}
}

private getTestStats(test: Test | Custom) {
const file = test.file
let stats = this.runningTests.get(file.id)

if (!stats) {
// It's possible that that test finished before it's preparation was even reported
this.onTestFilePrepare(test.file)
stats = this.runningTests.get(file.id)!

// It's also possible that this update came after whole test file was reported as finished
if (!stats) {
return
}
}

return stats
}

private getHookStats({ file, id, type }: HookOptions) {
// Track slow running hooks only on verbose mode
if (!this.options.verbose) {
return
}

const stats = this.runningTests.get(file.id)

if (!stats) {
return
}

return type === 'suite' ? stats : stats?.tests.get(id)
}

private createSummary() {
const summary = ['']

Expand Down
87 changes: 87 additions & 0 deletions packages/vitest/src/node/reporters/task-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import type { Custom, File, Task, TaskResultPack, Test } from '@vitest/runner'
import type { Vitest } from '../core'
import { getTests } from '@vitest/runner/utils'

export interface HookOptions {
name: string
file: File
id: File['id'] | Test['id']
type: Task['type']
}

export class TaskParser {
ctx!: Vitest

onInit(ctx: Vitest) {
this.ctx = ctx
}

onHookStart(_options: HookOptions) {}
onHookEnd(_options: HookOptions) {}

onTestStart(_test: Test | Custom) {}
onTestFinished(_test: Test | Custom) {}

onTestFilePrepare(_file: File) {}
onTestFileFinished(_file: File) {}

onTaskUpdate(packs: TaskResultPack[]) {
const startingTestFiles: File[] = []
const finishedTestFiles: File[] = []

const startingTests: (Test | Custom)[] = []
const finishedTests: (Test | Custom)[] = []

const startingHooks: HookOptions[] = []
const endingHooks: HookOptions[] = []

for (const pack of packs) {
const task = this.ctx.state.idMap.get(pack[0])

if (task?.type === 'suite' && 'filepath' in task && task.result?.state) {
if (task?.result?.state === 'run') {
startingTestFiles.push(task)
}
else {
// Skipped tests are not reported, do it manually
for (const test of getTests(task)) {
if (!test.result || test.result?.state === 'skip') {
finishedTests.push(test)
}
}

finishedTestFiles.push(task.file)
}
}

if (task?.type === 'test' || task?.type === 'custom') {
if (task.result?.state === 'run') {
startingTests.push(task)
}
else if (task.result?.hooks?.afterEach !== 'run') {
finishedTests.push(task)
}
}

if (task?.result?.hooks) {
for (const [hook, state] of Object.entries(task.result.hooks)) {
if (state === 'run') {
startingHooks.push({ name: hook, file: task.file, id: task.id, type: task.type })
}
else {
endingHooks.push({ name: hook, file: task.file, id: task.id, type: task.type })
}
}
}
}

endingHooks.forEach(hook => this.onHookEnd(hook))
finishedTests.forEach(test => this.onTestFinished(test))
finishedTestFiles.forEach(file => this.onTestFileFinished(file))

startingTestFiles.forEach(file => this.onTestFilePrepare(file))
startingTests.forEach(test => this.onTestStart(test))
startingHooks.forEach(hook => this.onHookStart(hook),
)
}
}
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 40 additions & 0 deletions test/reporters/fixtures/task-parser-tests/example-1.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { beforeAll, beforeEach, afterEach, afterAll, test, describe } from "vitest";
import { setTimeout } from "node:timers/promises";

beforeAll(async () => {
await setTimeout(100);
});

afterAll(async () => {
await setTimeout(100);
});

describe("some suite", async () => {
beforeEach(async () => {
await setTimeout(100);
});

test("some test", async () => {
await setTimeout(100);
});

afterEach(async () => {
await setTimeout(100);
});
});

test("Fast test 1", () => {
//
});

test.skip("Skipped test 1", () => {
//
});

test.concurrent("parallel slow tests 1.1", async () => {
await setTimeout(100);
});

test.concurrent("parallel slow tests 1.2", async () => {
await setTimeout(100);
});
Loading

0 comments on commit b7a381c

Please sign in to comment.