Skip to content

Commit

Permalink
perf(reporters): overall improvements (#3006)
Browse files Browse the repository at this point in the history
  • Loading branch information
AriPerkkio committed Mar 15, 2023
1 parent 1fe8286 commit 22ca0b6
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 36 deletions.
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
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
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

0 comments on commit 22ca0b6

Please sign in to comment.