Skip to content

Commit 22ca0b6

Browse files
authored
perf(reporters): overall improvements (#3006)
1 parent 1fe8286 commit 22ca0b6

File tree

3 files changed

+93
-36
lines changed

3 files changed

+93
-36
lines changed

packages/vitest/src/node/reporters/benchmark/table/tableRender.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ function renderBenchmark(task: Benchmark, tasks: Task[]): string {
101101
].join(' ')
102102
}
103103

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

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

155155
if (task.type === 'suite' && task.tasks.length > 0) {
156156
if (task.result?.state)
157-
output = output.concat(renderTree(task.tasks, options, level + 1))
157+
output.push(renderTree(task.tasks, options, level + 1))
158158
}
159159
idx++
160160
}

packages/vitest/src/node/reporters/renderers/dotRenderer.ts

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,64 @@ export interface DotRendererOptions {
77
logger: Logger
88
}
99

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

15-
function render(tasks: Task[]) {
12+
const check: Icon = { char: '·', color: c.green }
13+
const cross: Icon = { char: 'x', color: c.red }
14+
const pending: Icon = { char: '*', color: c.yellow }
15+
const skip: Icon = { char: '-', color: (char: string) => c.dim(c.gray(char)) }
16+
17+
function getIcon(task: Task) {
18+
if (task.mode === 'skip' || task.mode === 'todo')
19+
return skip
20+
switch (task.result?.state) {
21+
case 'pass':
22+
return check
23+
case 'fail':
24+
return cross
25+
default:
26+
return pending
27+
}
28+
}
29+
30+
function render(tasks: Task[]): string {
1631
const all = getTests(tasks)
17-
return all.map((i) => {
18-
if (i.mode === 'skip' || i.mode === 'todo')
19-
return skip
20-
switch (i.result?.state) {
21-
case 'pass':
22-
return check
23-
case 'fail':
24-
return cross
25-
default:
26-
return pending
32+
const output: string[] = []
33+
34+
// The log-update uses various ANSI helper utilities, e.g. ansi-warp, ansi-slice,
35+
// when printing. Passing it hundreds of single characters containing ANSI codes reduces
36+
// performances. We can optimize it by reducing amount of ANSI codes, e.g. by coloring
37+
// multiple tasks at once instead of each task separately.
38+
let currentIcon = pending
39+
let currentTasks = 0
40+
41+
const addOutput = () => output.push(currentIcon.color(currentIcon.char.repeat(currentTasks)))
42+
43+
for (const task of all) {
44+
const icon = getIcon(task)
45+
const isLast = all.indexOf(task) === all.length - 1
46+
47+
if (icon === currentIcon) {
48+
currentTasks++
49+
50+
if (isLast)
51+
addOutput()
52+
53+
continue
2754
}
28-
}).join('')
55+
56+
// Task mode/state has changed, add previous group to output
57+
addOutput()
58+
59+
// Start tracking new group
60+
currentTasks = 1
61+
currentIcon = icon
62+
63+
if (isLast)
64+
addOutput()
65+
}
66+
67+
return output.join('')
2968
}
3069

3170
export const createDotRenderer = (_tasks: Task[], options: DotRendererOptions) => {
@@ -42,12 +81,11 @@ export const createDotRenderer = (_tasks: Task[], options: DotRendererOptions) =
4281
start() {
4382
if (timer)
4483
return this
45-
timer = setInterval(update, 200)
84+
timer = setInterval(update, 16)
4685
return this
4786
},
4887
update(_tasks: Task[]) {
4988
tasks = _tasks
50-
update()
5189
return this
5290
},
5391
async stop() {

packages/vitest/src/node/reporters/renderers/listRenderer.ts

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function formatNumber(number: number) {
3535
+ (res[1] ? `.${res[1]}` : '')
3636
}
3737

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

89-
export function renderTree(tasks: Task[], options: ListRendererOptions, level = 0) {
90-
let output: string[] = []
89+
export function renderTree(tasks: Task[], options: ListRendererOptions, level = 0, maxRows?: number): string {
90+
const output: string[] = []
91+
let currentRowCount = 0
92+
93+
// Go through tasks in reverse order since maxRows is used to bail out early when limit is reached
94+
for (const task of [...tasks].reverse()) {
95+
const taskOutput = []
9196

92-
for (const task of tasks) {
9397
let suffix = ''
9498
let prefix = ` ${getStateSymbol(task)} `
9599

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

127-
output.push(padding + prefix + body + suffix)
131+
taskOutput.push(padding + prefix + body + suffix)
128132

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

137141
if (data != null) {
138142
const out = `${' '.repeat(level)}${F_RIGHT} ${data}`
139-
output.push(` ${c.gray(cliTruncate(out, getCols(-3)))}`)
143+
taskOutput.push(` ${c.gray(cliTruncate(out, getCols(-3)))}`)
140144
}
141145
}
142146

143-
output = output.concat(renderHookState(task, 'beforeAll', level + 1))
144-
output = output.concat(renderHookState(task, 'beforeEach', level + 1))
147+
taskOutput.push(renderHookState(task, 'beforeAll', level + 1))
148+
taskOutput.push(renderHookState(task, 'beforeEach', level + 1))
145149
if (task.type === 'suite' && task.tasks.length > 0) {
146150
if ((task.result?.state === 'fail' || task.result?.state === 'run' || options.renderSucceed))
147-
output = output.concat(renderTree(task.tasks, options, level + 1))
151+
taskOutput.push(renderTree(task.tasks, options, level + 1, maxRows))
148152
}
149-
output = output.concat(renderHookState(task, 'afterAll', level + 1))
150-
output = output.concat(renderHookState(task, 'afterEach', level + 1))
153+
taskOutput.push(renderHookState(task, 'afterAll', level + 1))
154+
taskOutput.push(renderHookState(task, 'afterEach', level + 1))
155+
156+
const rows = taskOutput.filter(Boolean)
157+
output.push(rows.join('\n'))
158+
currentRowCount += rows.length
159+
160+
if (maxRows && currentRowCount >= maxRows)
161+
break
151162
}
152163

153164
// TODO: moving windows
154-
return output.filter(Boolean).join('\n')
165+
return output.reverse().join('\n')
155166
}
156167

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

163174
function update() {
164-
log(renderTree(tasks, options))
175+
log(renderTree(
176+
tasks,
177+
options,
178+
0,
179+
// log-update already limits the amount of printed rows to fit the current terminal
180+
// but we can optimize performance by doing it ourselves
181+
process.stdout.rows,
182+
))
165183
}
166184

167185
return {
168186
start() {
169187
if (timer)
170188
return this
171-
timer = setInterval(update, 200)
189+
timer = setInterval(update, 16)
172190
return this
173191
},
174192
update(_tasks: Task[]) {
175193
tasks = _tasks
176-
update()
177194
return this
178195
},
179196
async stop() {
@@ -182,6 +199,8 @@ export const createListRenderer = (_tasks: Task[], options: ListRendererOptions)
182199
timer = undefined
183200
}
184201
log.clear()
202+
203+
// Note that at this point the renderTree should output all tasks
185204
options.logger.log(renderTree(tasks, options))
186205
return this
187206
},

0 commit comments

Comments
 (0)