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: add config option logHeapUsage #1225

Merged
merged 2 commits into from May 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/config/index.md
Expand Up @@ -448,3 +448,10 @@ export default {
},
}
```

### logHeapUsage

- **Type**: `boolean`
- **Default**: `false`

Show heap usage after each test. Usefull for debugging memory leaks.
10 changes: 8 additions & 2 deletions packages/vitest/src/node/reporters/base.ts
Expand Up @@ -16,6 +16,8 @@ const HELP_QUITE = `${c.dim('press ')}${c.bold('q')}${c.dim(' to quit')}`
const WAIT_FOR_CHANGE_PASS = `\n${c.bold(c.inverse(c.green(' PASS ')))}${c.green(' Waiting for file changes...')}`
const WAIT_FOR_CHANGE_FAIL = `\n${c.bold(c.inverse(c.red(' FAIL ')))}${c.red(' Tests failed. Watching for file changes...')}`

const DURATION_LONG = 300

export abstract class BaseReporter implements Reporter {
start = 0
end = 0
Expand Down Expand Up @@ -86,8 +88,12 @@ export abstract class BaseReporter implements Reporter {
if (skipped.length)
state += ` ${c.dim('|')} ${c.yellow(`${skipped.length} skipped`)}`
let suffix = c.dim(' (') + state + c.dim(')')
if (task.result.duration)
suffix += c.yellow(` ${Math.round(task.result.duration)}${c.dim('ms')}`)
if (task.result.duration) {
const color = task.result.duration > DURATION_LONG ? c.yellow : c.gray
suffix += color(` ${Math.round(task.result.duration)}${c.dim('ms')}`)
}
if (this.ctx.config.logHeapUsage && task.result.heap != null)
suffix += c.magenta(` ${Math.floor(task.result.heap / 1024 / 1024)} MB heap used`)

this.ctx.log(` ${getStateSymbol(task)} ${task.name} ${suffix}`)

Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/node/reporters/default.ts
Expand Up @@ -22,6 +22,7 @@ export class DefaultReporter extends BaseReporter {
onCollected() {
if (this.isTTY) {
this.rendererOptions.outputStream = this.ctx.outputStream
this.rendererOptions.showHeap = this.ctx.config.logHeapUsage
const files = this.ctx.state.getFiles(this.watchFilters)
if (!this.renderer)
this.renderer = createListRenderer(files, this.rendererOptions).start()
Expand Down
4 changes: 4 additions & 0 deletions packages/vitest/src/node/reporters/renderers/listRenderer.ts
Expand Up @@ -10,6 +10,7 @@ import { getCols, getHookStateSymbol, getStateSymbol } from './utils'
export interface ListRendererOptions {
renderSucceed?: boolean
outputStream: NodeJS.WritableStream
showHeap: boolean
}

const DURATION_LONG = 300
Expand Down Expand Up @@ -53,6 +54,9 @@ export function renderTree(tasks: Task[], options: ListRendererOptions, level =
suffix += c.yellow(` ${Math.round(task.result.duration)}${c.dim('ms')}`)
}

if (options.showHeap && task.result?.heap != null)
suffix += c.magenta(` ${Math.floor(task.result.heap / 1024 / 1024)} MB heap used`)

let name = task.name
if (level === 0)
name = formatFilepath(name)
Expand Down
5 changes: 4 additions & 1 deletion packages/vitest/src/node/reporters/verbose.ts
Expand Up @@ -17,7 +17,10 @@ export class VerboseReporter extends DefaultReporter {
for (const pack of packs) {
const task = this.ctx.state.idMap.get(pack[0])
if (task && task.type === 'test' && task.result?.state && task.result?.state !== 'run') {
this.ctx.log(` ${getStateSymbol(task)} ${getFullName(task)}`)
let title = ` ${getStateSymbol(task)} ${getFullName(task)}`
if (this.ctx.config.logHeapUsage && task.result.heap != null)
title += c.magenta(` ${Math.floor(task.result.heap / 1024 / 1024)} MB heap used`)
this.ctx.log(title)
if (task.result.state === 'fail')
this.ctx.log(c.red(` ${F_RIGHT} ${(task.result.error as any)?.message}`))
}
Expand Down
12 changes: 10 additions & 2 deletions packages/vitest/src/runtime/run.ts
Expand Up @@ -51,9 +51,9 @@ export async function callSuiteHook<T extends keyof SuiteHooks>(
return callbacks
}

const packs = new Map<string, TaskResult|undefined>()
const packs = new Map<string, TaskResult | undefined>()
let updateTimer: any
let previousUpdate: Promise<void>|undefined
let previousUpdate: Promise<void> | undefined

function updateTask(task: Task) {
packs.set(task.id, task.result)
Expand Down Expand Up @@ -151,6 +151,9 @@ export async function runTest(test: Test) {

test.result.duration = now() - start

if (workerState.config.logHeapUsage)
test.result.heap = process.memoryUsage().heapUsed

workerState.current = undefined

updateTask(test)
Expand Down Expand Up @@ -212,6 +215,11 @@ export async function runSuite(suite: Suite) {
}
suite.result.duration = now() - start

const workerState = getWorkerState()

if (workerState.config.logHeapUsage)
suite.result.heap = process.memoryUsage().heapUsed

if (suite.mode === 'run') {
if (!hasTests(suite)) {
suite.result.state = 'fail'
Expand Down
5 changes: 5 additions & 0 deletions packages/vitest/src/types/config.ts
Expand Up @@ -297,6 +297,11 @@ export interface InlineConfig {
* Resolve custom snapshot path
*/
resolveSnapshotPath?: (path: string, extension: string) => string

/**
* Show heap usage after each test. Usefull for debugging memory leaks.
*/
logHeapUsage?: boolean
}

export interface UserConfig extends InlineConfig {
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/types/tasks.ts
Expand Up @@ -20,6 +20,7 @@ export interface TaskResult {
state: TaskState
duration?: number
startTime?: number
heap?: number
error?: ErrorWithDiff
htmlError?: string
hooks?: Partial<Record<keyof SuiteHooks, TaskState>>
Expand Down