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

perf(reporters): overall improvements #3006

Merged
merged 3 commits into from
Mar 15, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ function renderBenchmark(task: Benchmark, tasks: Task[]): string {
].join(' ')
}

export function renderTree(tasks: Task[], options: ListRendererOptions, level = 0) {
let output: string[] = []
export function renderTree(tasks: Task[], options: ListRendererOptions, level = 0): string {
const output: string[] = []

let idx = 0
for (const task of tasks) {
Expand Down Expand Up @@ -154,7 +154,7 @@ export function renderTree(tasks: Task[], options: ListRendererOptions, level =

if (task.type === 'suite' && task.tasks.length > 0) {
if (task.result?.state)
output = output.concat(renderTree(task.tasks, options, level + 1))
output.push(renderTree(task.tasks, options, level + 1))
}
idx++
}
Expand Down
74 changes: 56 additions & 18 deletions packages/vitest/src/node/reporters/renderers/dotRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,64 @@ export interface DotRendererOptions {
logger: Logger
}

const check = c.green('·')
const cross = c.red('x')
const pending = c.yellow('*')
const skip = c.dim(c.gray('-'))
interface Icon { char: string; color: (char: string) => string }

function render(tasks: Task[]) {
const check: Icon = { char: '·', color: c.green }
const cross: Icon = { char: 'x', color: c.red }
const pending: Icon = { char: '*', color: c.yellow }
const skip: Icon = { char: '-', color: (char: string) => c.dim(c.gray(char)) }

function getIcon(task: Task) {
if (task.mode === 'skip' || task.mode === 'todo')
return skip
switch (task.result?.state) {
case 'pass':
return check
case 'fail':
return cross
default:
return pending
}
}

function render(tasks: Task[]): string {
const all = getTests(tasks)
return all.map((i) => {
if (i.mode === 'skip' || i.mode === 'todo')
return skip
switch (i.result?.state) {
case 'pass':
return check
case 'fail':
return cross
default:
return pending
const output: string[] = []

// The log-update uses various ANSI helper utilities, e.g. ansi-warp, ansi-slice,
// when printing. Passing it hundreds of single characters containing ANSI codes reduces
// performances. We can optimize it by reducing amount of ANSI codes, e.g. by coloring
// multiple tasks at once instead of each task separately.
let currentIcon = pending
let currentTasks = 0

const addOutput = () => output.push(currentIcon.color(currentIcon.char.repeat(currentTasks)))

for (const task of all) {
const icon = getIcon(task)
const isLast = all.indexOf(task) === all.length - 1

if (icon === currentIcon) {
currentTasks++

if (isLast)
addOutput()

continue
}
}).join('')

// Task mode/state has changed, add previous group to output
addOutput()

// Start tracking new group
currentTasks = 1
currentIcon = icon

if (isLast)
addOutput()
}

return output.join('')
}

export const createDotRenderer = (_tasks: Task[], options: DotRendererOptions) => {
Expand All @@ -42,12 +81,11 @@ export const createDotRenderer = (_tasks: Task[], options: DotRendererOptions) =
start() {
if (timer)
return this
timer = setInterval(update, 200)
timer = setInterval(update, 16)
return this
},
update(_tasks: Task[]) {
tasks = _tasks
update()
return this
},
async stop() {
Expand Down
49 changes: 34 additions & 15 deletions packages/vitest/src/node/reporters/renderers/listRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ function formatNumber(number: number) {
+ (res[1] ? `.${res[1]}` : '')
}

function renderHookState(task: Task, hookName: keyof SuiteHooks, level = 0) {
function renderHookState(task: Task, hookName: keyof SuiteHooks, level = 0): string {
const state = task.result?.hooks?.[hookName]
if (state && state === 'run')
return `${' '.repeat(level)} ${getHookStateSymbol(task, hookName)} ${c.dim(`[ ${hookName} ]`)}`
Expand Down Expand Up @@ -86,10 +86,14 @@ function renderBenchmark(task: Benchmark, tasks: Task[]): string {
].join('')
}

export function renderTree(tasks: Task[], options: ListRendererOptions, level = 0) {
let output: string[] = []
export function renderTree(tasks: Task[], options: ListRendererOptions, level = 0, maxRows?: number): string {
const output: string[] = []
let currentRowCount = 0

// Go through tasks in reverse order since maxRows is used to bail out early when limit is reached
for (const task of [...tasks].reverse()) {
const taskOutput = []

for (const task of tasks) {
let suffix = ''
let prefix = ` ${getStateSymbol(task)} `

Expand Down Expand Up @@ -124,7 +128,7 @@ export function renderTree(tasks: Task[], options: ListRendererOptions, level =
? renderBenchmark(task as Benchmark, tasks)
: name

output.push(padding + prefix + body + suffix)
taskOutput.push(padding + prefix + body + suffix)

if ((task.result?.state !== 'pass') && outputMap.get(task) != null) {
let data: string | undefined = outputMap.get(task)
Expand All @@ -136,22 +140,29 @@ export function renderTree(tasks: Task[], options: ListRendererOptions, level =

if (data != null) {
const out = `${' '.repeat(level)}${F_RIGHT} ${data}`
output.push(` ${c.gray(cliTruncate(out, getCols(-3)))}`)
taskOutput.push(` ${c.gray(cliTruncate(out, getCols(-3)))}`)
}
}

output = output.concat(renderHookState(task, 'beforeAll', level + 1))
output = output.concat(renderHookState(task, 'beforeEach', level + 1))
taskOutput.push(renderHookState(task, 'beforeAll', level + 1))
taskOutput.push(renderHookState(task, 'beforeEach', level + 1))
if (task.type === 'suite' && task.tasks.length > 0) {
if ((task.result?.state === 'fail' || task.result?.state === 'run' || options.renderSucceed))
output = output.concat(renderTree(task.tasks, options, level + 1))
taskOutput.push(renderTree(task.tasks, options, level + 1, maxRows))
}
output = output.concat(renderHookState(task, 'afterAll', level + 1))
output = output.concat(renderHookState(task, 'afterEach', level + 1))
taskOutput.push(renderHookState(task, 'afterAll', level + 1))
taskOutput.push(renderHookState(task, 'afterEach', level + 1))

const rows = taskOutput.filter(Boolean)
output.push(rows.join('\n'))
currentRowCount += rows.length

if (maxRows && currentRowCount >= maxRows)
break
}

// TODO: moving windows
return output.filter(Boolean).join('\n')
return output.reverse().join('\n')
}

export const createListRenderer = (_tasks: Task[], options: ListRendererOptions) => {
Expand All @@ -161,19 +172,25 @@ export const createListRenderer = (_tasks: Task[], options: ListRendererOptions)
const log = options.logger.logUpdate

function update() {
log(renderTree(tasks, options))
log(renderTree(
tasks,
options,
0,
// log-update already limits the amount of printed rows to fit the current terminal
// but we can optimize performance by doing it ourselves
process.stdout.rows,
))
}

return {
start() {
if (timer)
return this
timer = setInterval(update, 200)
timer = setInterval(update, 16)
return this
},
update(_tasks: Task[]) {
tasks = _tasks
update()
return this
},
async stop() {
Expand All @@ -182,6 +199,8 @@ export const createListRenderer = (_tasks: Task[], options: ListRendererOptions)
timer = undefined
}
log.clear()

// Note that at this point the renderTree should output all tasks
options.logger.log(renderTree(tasks, options))
return this
},
Expand Down