fix(benchmark): rewrite reporter without log-update (#7019)

This commit is contained in:
Ari Perkkiö 2024-12-26 10:35:10 +02:00 committed by GitHub
parent b700d26467
commit 6d23f4b11c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 452 additions and 923 deletions

View File

@ -225,57 +225,6 @@ Repository: https://github.com/acornjs/acorn.git
---------------------------------------
## ansi-escapes
License: MIT
By: Sindre Sorhus
Repository: sindresorhus/ansi-escapes
> MIT License
>
> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
>
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---------------------------------------
## ansi-regex
License: MIT
By: Sindre Sorhus
Repository: chalk/ansi-regex
> MIT License
>
> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
>
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---------------------------------------
## ansi-styles
License: MIT
By: Sindre Sorhus
Repository: chalk/ansi-styles
> MIT License
>
> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
>
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---------------------------------------
## birpc
License: MIT
By: Anthony Fu
@ -392,75 +341,6 @@ Repository: https://github.com/debitoor/chai-subset.git
---------------------------------------
## cli-cursor
License: MIT
By: Sindre Sorhus
Repository: sindresorhus/cli-cursor
> MIT License
>
> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
>
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---------------------------------------
## cli-truncate
License: MIT
By: Sindre Sorhus
Repository: sindresorhus/cli-truncate
> MIT License
>
> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
>
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---------------------------------------
## eastasianwidth
License: MIT
By: Masaki Komagata
Repository: git://github.com/komagata/eastasianwidth.git
---------------------------------------
## emoji-regex
License: MIT
By: Mathias Bynens
Repository: https://github.com/mathiasbynens/emoji-regex.git
> Copyright Mathias Bynens <https://mathiasbynens.be/>
>
> Permission is hereby granted, free of charge, to any person obtaining
> a copy of this software and associated documentation files (the
> "Software"), to deal in the Software without restriction, including
> without limitation the rights to use, copy, modify, merge, publish,
> distribute, sublicense, and/or sell copies of the Software, and to
> permit persons to whom the Software is furnished to do so, subject to
> the following conditions:
>
> The above copyright notice and this permission notice shall be
> included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
> NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
> LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
> OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
> WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---------------------------------------
## fast-glob
License: MIT
By: Denis Malinochkin
@ -580,23 +460,6 @@ Repository: git+https://github.com/WebReflection/flatted.git
---------------------------------------
## get-east-asian-width
License: MIT
By: Sindre Sorhus
Repository: sindresorhus/get-east-asian-width
> MIT License
>
> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
>
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---------------------------------------
## get-tsconfig
License: MIT
By: Hiroki Osame
@ -678,23 +541,6 @@ Repository: jonschlinkert/is-extglob
---------------------------------------
## is-fullwidth-code-point
License: MIT
By: Sindre Sorhus
Repository: sindresorhus/is-fullwidth-code-point
> MIT License
>
> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
>
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---------------------------------------
## is-glob
License: MIT
By: Jon Schlinkert, Brian Woodward, Daniel Perez
@ -857,23 +703,6 @@ Repository: sindresorhus/locate-path
---------------------------------------
## log-update
License: MIT
By: Sindre Sorhus
Repository: sindresorhus/log-update
> MIT License
>
> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
>
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---------------------------------------
## merge2
License: MIT
Repository: git@github.com:teambition/merge2.git
@ -931,23 +760,6 @@ Repository: micromatch/micromatch
---------------------------------------
## mimic-fn
License: MIT
By: Sindre Sorhus
Repository: sindresorhus/mimic-fn
> MIT License
>
> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
>
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---------------------------------------
## mlly
License: MIT
Repository: unjs/mlly
@ -976,23 +788,6 @@ Repository: unjs/mlly
---------------------------------------
## onetime
License: MIT
By: Sindre Sorhus
Repository: sindresorhus/onetime
> MIT License
>
> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
>
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---------------------------------------
## p-limit
License: MIT
By: Sindre Sorhus
@ -1171,23 +966,6 @@ Repository: privatenumber/resolve-pkg-maps
---------------------------------------
## restore-cursor
License: MIT
By: Sindre Sorhus
Repository: sindresorhus/restore-cursor
> MIT License
>
> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
>
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---------------------------------------
## reusify
License: MIT
By: Matteo Collina
@ -1245,30 +1023,6 @@ Repository: git://github.com/feross/run-parallel.git
---------------------------------------
## signal-exit
License: ISC
By: Ben Coe
Repository: https://github.com/tapjs/signal-exit.git
> The ISC License
>
> Copyright (c) 2015, Contributors
>
> Permission to use, copy, modify, and/or distribute this software
> for any purpose with or without fee is hereby granted, provided
> that the above copyright notice and this permission notice
> appear in all copies.
>
> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
> WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
> OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE
> LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
> OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
> WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
> ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
---------------------------------------
## sisteransi
License: MIT
By: Terkel Gjervig
@ -1298,57 +1052,6 @@ Repository: https://github.com/terkelg/sisteransi
---------------------------------------
## slice-ansi
License: MIT
Repository: chalk/slice-ansi
> MIT License
>
> Copyright (c) DC <threedeecee@gmail.com>
> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
>
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---------------------------------------
## string-width
License: MIT
By: Sindre Sorhus
Repository: sindresorhus/string-width
> MIT License
>
> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
>
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---------------------------------------
## strip-ansi
License: MIT
By: Sindre Sorhus
Repository: chalk/strip-ansi
> MIT License
>
> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
>
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---------------------------------------
## strip-literal
License: MIT
By: Anthony Fu
@ -1462,23 +1165,6 @@ Repository: unjs/ufo
---------------------------------------
## wrap-ansi
License: MIT
By: Sindre Sorhus
Repository: chalk/wrap-ansi
> MIT License
>
> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
>
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---------------------------------------
## ws
License: MIT
By: Einar Otto Stangvik

View File

@ -189,7 +189,6 @@
"birpc": "0.2.19",
"cac": "^6.7.14",
"chai-subset": "^1.6.0",
"cli-truncate": "^4.0.0",
"fast-glob": "3.3.2",
"find-up": "^6.3.0",
"flatted": "^3.3.2",
@ -197,7 +196,6 @@
"happy-dom": "^15.11.7",
"jsdom": "^25.0.1",
"local-pkg": "^0.5.1",
"log-update": "^5.0.1",
"micromatch": "^4.0.8",
"pretty-format": "^29.7.0",
"prompts": "^2.4.2",

View File

@ -1113,7 +1113,6 @@ export class Vitest {
this.logger.error('error during close', r.reason)
}
})
this.logger.logUpdate.done() // restore terminal cursor
})
})()
}

View File

@ -7,7 +7,6 @@ import { existsSync, readFileSync } from 'node:fs'
import { Writable } from 'node:stream'
import { stripVTControlCharacters } from 'node:util'
import { inspect, isPrimitive } from '@vitest/utils'
import cliTruncate from 'cli-truncate'
import { normalize, relative } from 'pathe'
import c from 'tinyrainbow'
import { TypeCheckError } from '../typecheck/typechecker'
@ -17,7 +16,7 @@ import {
} from '../utils/source-map'
import { Logger } from './logger'
import { F_POINTER } from './reporters/renderers/figures'
import { divider } from './reporters/renderers/utils'
import { divider, truncateString } from './reporters/renderers/utils'
interface PrintErrorOptions {
type?: string
@ -413,7 +412,7 @@ export function generateCodeFrame(
res.push(
lineNo(j + 1)
+ cliTruncate(lines[j].replace(/\t/g, ' '), columns - 5 - indent),
+ truncateString(lines[j].replace(/\t/g, ' '), columns - 5 - indent),
)
if (j === i) {

View File

@ -8,7 +8,6 @@ import type { TestProject } from './project'
import { Console } from 'node:console'
import { toArray } from '@vitest/utils'
import { parseErrorStacktrace } from '@vitest/utils/source-map'
import { createLogUpdate } from 'log-update'
import c from 'tinyrainbow'
import { highlightCode } from '../utils/colors'
import { printError } from './error'
@ -25,19 +24,22 @@ export interface ErrorOptions {
showCodeFrame?: boolean
}
type Listener = () => void
const PAD = ' '
const ESC = '\x1B['
const ERASE_DOWN = `${ESC}J`
const ERASE_SCROLLBACK = `${ESC}3J`
const CURSOR_TO_START = `${ESC}1;1H`
const HIDE_CURSOR = `${ESC}?25l`
const SHOW_CURSOR = `${ESC}?25h`
const CLEAR_SCREEN = '\x1Bc'
export class Logger {
logUpdate: ReturnType<typeof createLogUpdate>
private _clearScreenPending: string | undefined
private _highlights = new Map<string, string>()
private cleanupListeners: Listener[] = []
public console: Console
constructor(
@ -46,9 +48,11 @@ export class Logger {
public errorStream: NodeJS.WriteStream | Writable = process.stderr,
) {
this.console = new Console({ stdout: outputStream, stderr: errorStream })
this.logUpdate = createLogUpdate(this.outputStream)
this._highlights.clear()
this.addCleanupListeners()
this.registerUnhandledRejection()
;(this.outputStream as Writable).write(HIDE_CURSOR)
}
log(...args: any[]) {
@ -303,6 +307,44 @@ export class Logger {
this.log(c.red(divider()))
}
getColumns() {
return 'columns' in this.outputStream ? this.outputStream.columns : 80
}
onTerminalCleanup(listener: Listener) {
this.cleanupListeners.push(listener)
}
private addCleanupListeners() {
const cleanup = () => {
this.cleanupListeners.forEach(fn => fn())
;(this.outputStream as Writable).write(SHOW_CURSOR)
}
const onExit = (signal?: string | number, exitCode?: number) => {
cleanup()
// Interrupted signals don't set exit code automatically.
// Use same exit code as node: https://nodejs.org/api/process.html#signal-events
if (process.exitCode === undefined) {
process.exitCode = exitCode !== undefined ? (128 + exitCode) : Number(signal)
}
process.exit()
}
process.once('SIGINT', onExit)
process.once('SIGTERM', onExit)
process.once('exit', onExit)
this.ctx.onClose(() => {
process.off('SIGINT', onExit)
process.off('SIGTERM', onExit)
process.off('exit', onExit)
cleanup()
})
}
private registerUnhandledRejection() {
const onUnhandledRejection = (err: unknown) => {
process.exitCode = 1

View File

@ -71,6 +71,9 @@ export abstract class BaseReporter implements Reporter {
}
}
/**
* Callback invoked with a single `Task` from `onTaskUpdate`
*/
protected printTask(task: Task) {
if (
!('filepath' in task)
@ -438,7 +441,7 @@ export abstract class BaseReporter implements Reporter {
const benches = getTests(files)
const topBenches = benches.filter(i => i.result?.benchmark?.rank === 1)
this.log(withLabel('cyan', 'BENCH', 'Summary\n'))
this.log(`\n${withLabel('cyan', 'BENCH', 'Summary\n')}`)
for (const bench of topBenches) {
const group = bench.suite || bench.file
@ -448,7 +451,7 @@ export abstract class BaseReporter implements Reporter {
}
const groupName = getFullName(group, c.dim(' > '))
this.log(` ${bench.name}${c.dim(` - ${groupName}`)}`)
this.log(` ${formatProjectName(bench.file.projectName)}${bench.name}${c.dim(` - ${groupName}`)}`)
const siblings = group.tasks
.filter(i => i.meta.benchmark && i.result?.benchmark && i !== bench)

View File

@ -1,8 +1,9 @@
import { VerboseReporter } from '../verbose'
import { TableReporter } from './table'
import { BenchmarkReporter } from './reporter'
import { VerboseBenchmarkReporter } from './verbose'
export const BenchmarkReportsMap = {
default: TableReporter,
verbose: VerboseReporter,
default: BenchmarkReporter,
verbose: VerboseBenchmarkReporter,
}
export type BenchmarkBuiltinReporters = keyof typeof BenchmarkReportsMap

View File

@ -0,0 +1,69 @@
import type { File } from '@vitest/runner'
import type { BenchmarkResult } from '../../../runtime/types/benchmark'
import { getFullName, getTasks } from '@vitest/runner/utils'
interface Report {
files: {
filepath: string
groups: Group[]
}[]
}
interface Group {
fullName: string
benchmarks: FormattedBenchmarkResult[]
}
export type FormattedBenchmarkResult = BenchmarkResult & {
id: string
}
export function createBenchmarkJsonReport(files: File[]) {
const report: Report = { files: [] }
for (const file of files) {
const groups: Group[] = []
for (const task of getTasks(file)) {
if (task?.type === 'suite') {
const benchmarks: FormattedBenchmarkResult[] = []
for (const t of task.tasks) {
const benchmark = t.meta.benchmark && t.result?.benchmark
if (benchmark) {
benchmarks.push({ id: t.id, ...benchmark, samples: [] })
}
}
if (benchmarks.length) {
groups.push({
fullName: getFullName(task, ' > '),
benchmarks,
})
}
}
}
report.files.push({
filepath: file.filepath,
groups,
})
}
return report
}
export function flattenFormattedBenchmarkReport(report: Report) {
const flat: Record<FormattedBenchmarkResult['id'], FormattedBenchmarkResult> = {}
for (const file of report.files) {
for (const group of file.groups) {
for (const t of group.benchmarks) {
flat[t.id] = t
}
}
}
return flat
}

View File

@ -0,0 +1,97 @@
import type { Task, TaskResultPack } from '@vitest/runner'
import type { Vitest } from '../../core'
import fs from 'node:fs'
import { getFullName } from '@vitest/runner/utils'
import * as pathe from 'pathe'
import c from 'tinyrainbow'
import { DefaultReporter } from '../default'
import { formatProjectName, getStateSymbol } from '../renderers/utils'
import { createBenchmarkJsonReport, flattenFormattedBenchmarkReport } from './json-formatter'
import { renderTable } from './tableRender'
export class BenchmarkReporter extends DefaultReporter {
compare?: Parameters<typeof renderTable>[0]['compare']
async onInit(ctx: Vitest) {
super.onInit(ctx)
if (this.ctx.config.benchmark?.compare) {
const compareFile = pathe.resolve(
this.ctx.config.root,
this.ctx.config.benchmark?.compare,
)
try {
this.compare = flattenFormattedBenchmarkReport(
JSON.parse(await fs.promises.readFile(compareFile, 'utf-8')),
)
}
catch (e) {
this.error(`Failed to read '${compareFile}'`, e)
}
}
}
onTaskUpdate(packs: TaskResultPack[]): void {
for (const pack of packs) {
const task = this.ctx.state.idMap.get(pack[0])
if (task?.type === 'suite' && task.result?.state !== 'run') {
task.tasks.filter(task => task.result?.benchmark)
.sort((benchA, benchB) => benchA.result!.benchmark!.mean - benchB.result!.benchmark!.mean)
.forEach((bench, idx) => {
bench.result!.benchmark!.rank = Number(idx) + 1
})
}
}
super.onTaskUpdate(packs)
}
printTask(task: Task) {
if (task?.type !== 'suite' || !task.result?.state || task.result?.state === 'run' || task.result?.state === 'queued') {
return
}
const benches = task.tasks.filter(t => t.meta.benchmark)
const duration = task.result.duration
if (benches.length > 0 && benches.every(t => t.result?.state !== 'run' && t.result?.state !== 'queued')) {
let title = `\n ${getStateSymbol(task)} ${formatProjectName(task.file.projectName)}${getFullName(task, c.dim(' > '))}`
if (duration != null && duration > this.ctx.config.slowTestThreshold) {
title += c.yellow(` ${Math.round(duration)}${c.dim('ms')}`)
}
this.log(title)
this.log(renderTable({
tasks: benches,
level: 1,
shallow: true,
columns: this.ctx.logger.getColumns(),
compare: this.compare,
showHeap: this.ctx.config.logHeapUsage,
slowTestThreshold: this.ctx.config.slowTestThreshold,
}))
}
}
async onFinished(files = this.ctx.state.getFiles(), errors = this.ctx.state.getUnhandledErrors()) {
super.onFinished(files, errors)
// write output for future comparison
let outputFile = this.ctx.config.benchmark?.outputJson
if (outputFile) {
outputFile = pathe.resolve(this.ctx.config.root, outputFile)
const outputDirectory = pathe.dirname(outputFile)
if (!fs.existsSync(outputDirectory)) {
await fs.promises.mkdir(outputDirectory, { recursive: true })
}
const output = createBenchmarkJsonReport(files)
await fs.promises.writeFile(outputFile, JSON.stringify(output, null, 2))
this.log(`Benchmark report written to ${outputFile}`)
}
}
}

View File

@ -1,214 +0,0 @@
import type { File, TaskResultPack } from '@vitest/runner'
import type { BenchmarkResult } from '../../../../runtime/types/benchmark'
import type { UserConsoleLog } from '../../../../types/general'
import fs from 'node:fs'
import { getFullName, getTasks } from '@vitest/runner/utils'
import * as pathe from 'pathe'
import c from 'tinyrainbow'
import { BaseReporter } from '../../base'
import { getStateSymbol } from '../../renderers/utils'
import {
createTableRenderer,
renderTree,
type TableRendererOptions,
} from './tableRender'
export class TableReporter extends BaseReporter {
renderer?: ReturnType<typeof createTableRenderer>
rendererOptions: TableRendererOptions = {} as any
onTestRemoved(trigger?: string) {
this.stopListRender()
this.ctx.logger.clearScreen(
c.yellow('Test removed...')
+ (trigger ? c.dim(` [ ${this.relative(trigger)} ]\n`) : ''),
true,
)
const files = this.ctx.state.getFiles(this.watchFilters)
createTableRenderer(files, this.rendererOptions).stop()
this.ctx.logger.log()
super.reportSummary(files, this.ctx.state.getUnhandledErrors())
super.onWatcherStart()
}
async onCollected() {
this.rendererOptions.logger = this.ctx.logger
this.rendererOptions.showHeap = this.ctx.config.logHeapUsage
this.rendererOptions.slowTestThreshold = this.ctx.config.slowTestThreshold
if (this.ctx.config.benchmark?.compare) {
const compareFile = pathe.resolve(
this.ctx.config.root,
this.ctx.config.benchmark?.compare,
)
try {
this.rendererOptions.compare = flattenFormattedBenchmarkReport(
JSON.parse(
await fs.promises.readFile(compareFile, 'utf-8'),
),
)
}
catch (e) {
this.ctx.logger.error(`Failed to read '${compareFile}'`, e)
}
}
if (this.isTTY) {
const files = this.ctx.state.getFiles(this.watchFilters)
if (!this.renderer) {
this.renderer = createTableRenderer(
files,
this.rendererOptions,
).start()
}
else {
this.renderer.update(files)
}
}
}
onTaskUpdate(packs: TaskResultPack[]) {
if (this.isTTY) {
return
}
for (const pack of packs) {
const task = this.ctx.state.idMap.get(pack[0])
if (
task
&& task.type === 'suite'
&& task.result?.state
&& task.result?.state !== 'run'
&& task.result?.state !== 'queued'
) {
// render static table when all benches inside single suite are finished
const benches = task.tasks.filter(t => t.meta.benchmark)
if (
benches.length > 0
&& benches.every(t => t.result?.state !== 'run' && t.result?.state !== 'queued')
) {
let title = ` ${getStateSymbol(task)} ${getFullName(
task,
c.dim(' > '),
)}`
if (
task.result.duration != null
&& task.result.duration > this.ctx.config.slowTestThreshold
) {
title += c.yellow(
` ${Math.round(task.result.duration)}${c.dim('ms')}`,
)
}
this.ctx.logger.log(title)
this.ctx.logger.log(
renderTree(benches, this.rendererOptions, 1, true),
)
}
}
}
}
async onFinished(
files = this.ctx.state.getFiles(),
errors = this.ctx.state.getUnhandledErrors(),
) {
this.stopListRender()
this.ctx.logger.log()
super.onFinished(files, errors)
// write output for future comparison
let outputFile = this.ctx.config.benchmark?.outputJson
if (outputFile) {
outputFile = pathe.resolve(this.ctx.config.root, outputFile)
const outputDirectory = pathe.dirname(outputFile)
if (!fs.existsSync(outputDirectory)) {
await fs.promises.mkdir(outputDirectory, { recursive: true })
}
const output = createFormattedBenchmarkReport(files)
await fs.promises.writeFile(outputFile, JSON.stringify(output, null, 2))
this.ctx.logger.log(`Benchmark report written to ${outputFile}`)
}
}
async onWatcherStart() {
this.stopListRender()
await super.onWatcherStart()
}
stopListRender() {
this.renderer?.stop()
this.renderer = undefined
}
async onWatcherRerun(files: string[], trigger?: string) {
this.stopListRender()
await super.onWatcherRerun(files, trigger)
}
onUserConsoleLog(log: UserConsoleLog) {
if (!this.shouldLog(log)) {
return
}
this.renderer?.clear()
super.onUserConsoleLog(log)
}
}
export interface FormattedBenchmarkReport {
files: {
filepath: string
groups: FormattedBenchmarkGroup[]
}[]
}
// flat results with TaskId as a key
export interface FlatBenchmarkReport {
[id: string]: FormattedBenchmarkResult
}
interface FormattedBenchmarkGroup {
fullName: string
benchmarks: FormattedBenchmarkResult[]
}
export type FormattedBenchmarkResult = BenchmarkResult & {
id: string
}
function createFormattedBenchmarkReport(files: File[]) {
const report: FormattedBenchmarkReport = { files: [] }
for (const file of files) {
const groups: FormattedBenchmarkGroup[] = []
for (const task of getTasks(file)) {
if (task && task.type === 'suite') {
const benchmarks: FormattedBenchmarkResult[] = []
for (const t of task.tasks) {
const benchmark = t.meta.benchmark && t.result?.benchmark
if (benchmark) {
benchmarks.push({ id: t.id, ...benchmark, samples: [] })
}
}
if (benchmarks.length) {
groups.push({
fullName: getFullName(task, ' > '),
benchmarks,
})
}
}
}
report.files.push({
filepath: file.filepath,
groups,
})
}
return report
}
function flattenFormattedBenchmarkReport(report: FormattedBenchmarkReport): FlatBenchmarkReport {
const flat: FlatBenchmarkReport = {}
for (const file of report.files) {
for (const group of file.groups) {
for (const t of group.benchmarks) {
flat[t.id] = t
}
}
}
return flat
}

View File

@ -1,46 +1,19 @@
import type { Task } from '@vitest/runner'
import type { FlatBenchmarkReport } from '.'
import type { BenchmarkResult } from '../../../../runtime/types/benchmark'
import type { Logger } from '../../../logger'
import type { BenchmarkResult } from '../../../runtime/types/benchmark'
import type { FormattedBenchmarkResult } from './json-formatter'
import { stripVTControlCharacters } from 'node:util'
import { getTests } from '@vitest/runner/utils'
import { notNullish } from '@vitest/utils'
import cliTruncate from 'cli-truncate'
import c from 'tinyrainbow'
import { F_RIGHT } from '../../renderers/figures'
import { getCols, getStateSymbol } from '../../renderers/utils'
export interface TableRendererOptions {
renderSucceed?: boolean
logger: Logger
showHeap: boolean
slowTestThreshold: number
compare?: FlatBenchmarkReport
}
import { F_RIGHT } from '../renderers/figures'
import { getStateSymbol, truncateString } from '../renderers/utils'
const outputMap = new WeakMap<Task, string>()
function formatFilepath(path: string) {
const lastSlash = Math.max(path.lastIndexOf('/') + 1, 0)
const basename = path.slice(lastSlash)
let firstDot = basename.indexOf('.')
if (firstDot < 0) {
firstDot = basename.length
}
firstDot += lastSlash
return (
c.dim(path.slice(0, lastSlash))
+ path.slice(lastSlash, firstDot)
+ c.dim(path.slice(firstDot))
)
}
function formatNumber(number: number) {
const res = String(number.toFixed(number < 100 ? 4 : 2)).split('.')
return (
res[0].replace(/(?=(?:\d{3})+$)\B/g, ',') + (res[1] ? `.${res[1]}` : '')
)
return res[0].replace(/(?=(?:\d{3})+$)\B/g, ',') + (res[1] ? `.${res[1]}` : '')
}
const tableHead = [
@ -75,6 +48,7 @@ function renderBenchmarkItems(result: BenchmarkResult) {
function computeColumnWidths(results: BenchmarkResult[]): number[] {
const rows = [tableHead, ...results.map(v => renderBenchmarkItems(v))]
return Array.from(tableHead, (_, i) =>
Math.max(...rows.map(row => stripVTControlCharacters(row[i]).length)))
}
@ -106,32 +80,32 @@ function renderBenchmark(result: BenchmarkResult, widths: number[]) {
].join(' ')
}
export function renderTree(
tasks: Task[],
options: TableRendererOptions,
level = 0,
shallow = false,
export function renderTable(
options: {
tasks: Task[]
level: number
shallow?: boolean
showHeap: boolean
columns: number
slowTestThreshold: number
compare?: Record<Task['id'], FormattedBenchmarkResult>
},
): string {
const output: string[] = []
const benchMap: Record<
string,
{ current: BenchmarkResult; baseline?: BenchmarkResult }
> = {}
for (const t of tasks) {
if (t.meta.benchmark && t.result?.benchmark) {
benchMap[t.id] = {
current: t.result.benchmark,
}
const baseline = options.compare?.[t.id]
if (baseline) {
benchMap[t.id].baseline = baseline
const benchMap: Record<string, { current: BenchmarkResult; baseline?: BenchmarkResult } > = {}
for (const task of options.tasks) {
if (task.meta.benchmark && task.result?.benchmark) {
benchMap[task.id] = {
current: task.result.benchmark,
baseline: options.compare?.[task.id],
}
}
}
const benchCount = Object.entries(benchMap).length
// compute column widths
const columnWidths = computeColumnWidths(
Object.values(benchMap)
.flatMap(v => [v.current, v.baseline])
@ -139,9 +113,14 @@ export function renderTree(
)
let idx = 0
for (const task of tasks) {
const padding = ' '.repeat(level ? 1 : 0)
const padding = ' '.repeat(options.level ? 1 : 0)
for (const task of options.tasks) {
const duration = task.result?.duration
const bench = benchMap[task.id]
let prefix = ''
if (idx === 0 && task.meta?.benchmark) {
prefix += `${renderTableHead(columnWidths)}\n${padding}`
}
@ -149,72 +128,67 @@ export function renderTree(
prefix += ` ${getStateSymbol(task)} `
let suffix = ''
if (task.type === 'suite') {
suffix += c.dim(` (${getTests(task).length})`)
}
if (task.mode === 'skip' || task.mode === 'todo') {
suffix += ` ${c.dim(c.gray('[skipped]'))}`
suffix += c.dim(c.gray(' [skipped]'))
}
if (task.result?.duration != null) {
if (task.result.duration > options.slowTestThreshold) {
suffix += c.yellow(
` ${Math.round(task.result.duration)}${c.dim('ms')}`,
)
}
if (duration != null && duration > options.slowTestThreshold) {
suffix += c.yellow(` ${Math.round(duration)}${c.dim('ms')}`)
}
if (options.showHeap && task.result?.heap != null) {
suffix += c.magenta(
` ${Math.floor(task.result.heap / 1024 / 1024)} MB heap used`,
)
suffix += c.magenta(` ${Math.floor(task.result.heap / 1024 / 1024)} MB heap used`)
}
let name = task.name
if (level === 0) {
name = formatFilepath(name)
}
const bench = benchMap[task.id]
if (bench) {
let body = renderBenchmark(bench.current, columnWidths)
if (options.compare && bench.baseline) {
if (bench.current.hz) {
const diff = bench.current.hz / bench.baseline.hz
const diffFixed = diff.toFixed(2)
if (diffFixed === '1.0.0') {
body += ` ${c.gray(`[${diffFixed}x]`)}`
body += c.gray(` [${diffFixed}x]`)
}
if (diff > 1) {
body += ` ${c.blue(`[${diffFixed}x] ⇑`)}`
body += c.blue(` [${diffFixed}x] ⇑`)
}
else {
body += ` ${c.red(`[${diffFixed}x] ⇓`)}`
body += c.red(` [${diffFixed}x] ⇓`)
}
}
output.push(padding + prefix + body + suffix)
const bodyBaseline = renderBenchmark(bench.baseline, columnWidths)
output.push(`${padding} ${bodyBaseline} ${c.dim('(baseline)')}`)
}
else {
if (bench.current.rank === 1 && benchCount > 1) {
body += ` ${c.bold(c.green(' fastest'))}`
body += c.bold(c.green(' fastest'))
}
if (bench.current.rank === benchCount && benchCount > 2) {
body += ` ${c.bold(c.gray(' slowest'))}`
body += c.bold(c.gray(' slowest'))
}
output.push(padding + prefix + body + suffix)
}
}
else {
output.push(padding + prefix + name + suffix)
output.push(padding + prefix + task.name + suffix)
}
if (task.result?.state !== 'pass' && outputMap.get(task) != null) {
let data: string | undefined = outputMap.get(task)
if (typeof data === 'string') {
data = stripVTControlCharacters(data.trim().split('\n').filter(Boolean).pop()!)
if (data === '') {
@ -223,14 +197,19 @@ export function renderTree(
}
if (data != null) {
const out = `${' '.repeat(level)}${F_RIGHT} ${data}`
output.push(` ${c.gray(cliTruncate(out, getCols(-3)))}`)
const out = ` ${' '.repeat(options.level)}${F_RIGHT} ${data}`
output.push(c.gray(truncateString(out, options.columns)))
}
}
if (!shallow && task.type === 'suite' && task.tasks.length > 0) {
if (!options.shallow && task.type === 'suite' && task.tasks.length > 0) {
if (task.result?.state) {
output.push(renderTree(task.tasks, options, level + 1))
output.push(renderTable({
...options,
tasks: task.tasks,
level: options.level + 1,
shallow: false,
}))
}
}
idx++
@ -238,44 +217,3 @@ export function renderTree(
return output.filter(Boolean).join('\n')
}
export function createTableRenderer(
_tasks: Task[],
options: TableRendererOptions,
) {
let tasks = _tasks
let timer: any
const log = options.logger.logUpdate
function update() {
log(renderTree(tasks, options))
}
return {
start() {
if (timer) {
return this
}
timer = setInterval(update, 200)
return this
},
update(_tasks: Task[]) {
tasks = _tasks
update()
return this
},
stop() {
if (timer) {
clearInterval(timer)
timer = undefined
}
log.clear()
options.logger.log(renderTree(tasks, options))
return this
},
clear() {
log.clear()
},
}
}

View File

@ -0,0 +1,5 @@
import { BenchmarkReporter } from './reporter'
export class VerboseBenchmarkReporter extends BenchmarkReporter {
protected verbose = true
}

View File

@ -96,7 +96,7 @@ class DotSummary extends TaskParser {
}
onTestFileFinished() {
const columns = this.renderer.getColumns()
const columns = this.ctx.logger.getColumns()
if (this.tests.size < columns) {
return

View File

@ -14,18 +14,15 @@ import {
F_POINTER,
} from './figures'
export const spinnerMap = new WeakMap<Task, () => string>()
export const hookSpinnerMap = new WeakMap<Task, Map<string, () => string>>()
export const pointer = c.yellow(F_POINTER)
export const skipped = c.dim(c.gray(F_DOWN))
export const benchmarkPass = c.green(F_DOT)
export const testPass = c.green(F_CHECK)
export const taskFail = c.red(F_CROSS)
export const suiteFail = c.red(F_POINTER)
export const pending = c.gray('·')
export function getCols(delta = 0) {
function getCols(delta = 0) {
let length = process.stdout?.columns
if (!length || Number.isNaN(length)) {
length = 30
@ -167,12 +164,6 @@ export function getStateSymbol(task: Task) {
if (task.type === 'suite') {
return pointer
}
let spinner = spinnerMap.get(task)
if (!spinner) {
spinner = elegantSpinner()
spinnerMap.set(task, spinner)
}
return c.yellow(spinner())
}
if (task.result.state === 'pass') {
@ -186,20 +177,6 @@ export function getStateSymbol(task: Task) {
return ' '
}
export const spinnerFrames
= process.platform === 'win32'
? ['-', '\\', '|', '/']
: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
export function elegantSpinner() {
let index = 0
return () => {
index = ++index % spinnerFrames.length
return spinnerFrames[index]
}
}
export function duration(time: number, locale = 'en-us') {
if (time < 1) {
return `${Number((time * 1e3).toFixed(2)).toLocaleString(locale)} ps`
@ -258,3 +235,13 @@ export function withLabel(color: 'red' | 'green' | 'blue' | 'cyan' | 'yellow', l
export function padSummaryTitle(str: string) {
return c.dim(`${str.padStart(11)} `)
}
export function truncateString(text: string, maxLength: number): string {
const plainText = stripVTControlCharacters(text)
if (plainText.length <= maxLength) {
return text
}
return `${plainText.slice(0, maxLength - 1)}`
}

View File

@ -7,8 +7,6 @@ const DEFAULT_RENDER_INTERVAL = 16
const ESC = '\x1B['
const CLEAR_LINE = `${ESC}K`
const MOVE_CURSOR_ONE_ROW_UP = `${ESC}1A`
const HIDE_CURSOR = `${ESC}?25l`
const SHOW_CURSOR = `${ESC}?25h`
const SYNC_START = `${ESC}?2026h`
const SYNC_END = `${ESC}?2026l`
@ -48,10 +46,13 @@ export class WindowRenderer {
this.cleanups.push(
this.interceptStream(process.stdout, 'output'),
this.interceptStream(process.stderr, 'error'),
this.addProcessExitListeners(),
)
this.write(HIDE_CURSOR, 'output')
// Write buffered content on unexpected exits, e.g. direct `process.exit()` calls
this.options.logger.onTerminalCleanup(() => {
this.flushBuffer()
this.stop()
})
this.start()
}
@ -62,7 +63,6 @@ export class WindowRenderer {
}
stop() {
this.write(SHOW_CURSOR, 'output')
this.cleanups.splice(0).map(fn => fn())
clearInterval(this.renderInterval)
}
@ -77,10 +77,6 @@ export class WindowRenderer {
clearInterval(this.renderInterval)
}
getColumns() {
return 'columns' in this.options.logger.outputStream ? this.options.logger.outputStream.columns : 80
}
private flushBuffer() {
if (this.buffer.length === 0) {
return this.render()
@ -116,11 +112,11 @@ export class WindowRenderer {
}
const windowContent = this.options.getWindow()
const rowCount = getRenderedRowCount(windowContent, this.getColumns())
const rowCount = getRenderedRowCount(windowContent, this.options.logger.getColumns())
let padding = this.windowHeight - rowCount
if (padding > 0 && message) {
padding -= getRenderedRowCount([message], this.getColumns())
padding -= getRenderedRowCount([message], this.options.logger.getColumns())
}
this.write(SYNC_START)
@ -178,32 +174,6 @@ export class WindowRenderer {
private write(message: string, type: 'output' | 'error' = 'output') {
(this.streams[type] as Writable['write'])(message)
}
private addProcessExitListeners() {
const onExit = (signal?: string | number, exitCode?: number) => {
// Write buffered content on unexpected exits, e.g. direct `process.exit()` calls
this.flushBuffer()
this.stop()
// Interrupted signals don't set exit code automatically.
// Use same exit code as node: https://nodejs.org/api/process.html#signal-events
if (process.exitCode === undefined) {
process.exitCode = exitCode !== undefined ? (128 + exitCode) : Number(signal)
}
process.exit()
}
process.once('SIGINT', onExit)
process.once('SIGTERM', onExit)
process.once('exit', onExit)
return function cleanup() {
process.off('SIGINT', onExit)
process.off('SIGTERM', onExit)
process.off('exit', onExit)
}
}
}
/** Calculate the actual row count needed to render `rows` into `stream` */

View File

@ -1,4 +1,4 @@
import type { TaskResultPack } from '@vitest/runner'
import type { Task } from '@vitest/runner'
import { getFullName } from '@vitest/runner/utils'
import c from 'tinyrainbow'
import { DefaultReporter } from './default'
@ -9,47 +9,40 @@ export class VerboseReporter extends DefaultReporter {
protected verbose = true
renderSucceed = true
onTaskUpdate(packs: TaskResultPack[]) {
printTask(task: Task): void {
if (this.isTTY) {
return super.onTaskUpdate(packs)
return super.printTask(task)
}
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'
&& task.result?.state !== 'queued'
) {
let title = ` ${getStateSymbol(task)} `
if (task.file.projectName) {
title += formatProjectName(task.file.projectName)
}
title += getFullName(task, c.dim(' > '))
if (
task.result.duration != null
&& task.result.duration > this.ctx.config.slowTestThreshold
) {
title += c.yellow(
` ${Math.round(task.result.duration)}${c.dim('ms')}`,
)
}
if (this.ctx.config.logHeapUsage && task.result.heap != null) {
title += c.magenta(
` ${Math.floor(task.result.heap / 1024 / 1024)} MB heap used`,
)
}
if (task.result?.note) {
title += c.dim(c.gray(` [${task.result.note}]`))
}
this.ctx.logger.log(title)
if (task.result.state === 'fail') {
task.result.errors?.forEach((error) => {
this.ctx.logger.log(c.red(` ${F_RIGHT} ${error?.message}`))
})
}
}
if (task.type !== 'test' || !task.result?.state || task.result?.state === 'run' || task.result?.state === 'queued') {
return
}
const duration = task.result.duration
let title = ` ${getStateSymbol(task)} `
if (task.file.projectName) {
title += formatProjectName(task.file.projectName)
}
title += getFullName(task, c.dim(' > '))
if (duration != null && duration > this.ctx.config.slowTestThreshold) {
title += c.yellow(` ${Math.round(duration)}${c.dim('ms')}`)
}
if (this.ctx.config.logHeapUsage && task.result.heap != null) {
title += c.magenta(` ${Math.floor(task.result.heap / 1024 / 1024)} MB heap used`)
}
if (task.result?.note) {
title += c.dim(c.gray(` [${task.result.note}]`))
}
this.ctx.logger.log(title)
if (task.result.state === 'fail') {
task.result.errors?.forEach(error => this.log(c.red(` ${F_RIGHT} ${error?.message}`)))
}
}
}

View File

@ -55,7 +55,6 @@ export function registerConsoleShortcuts(
|| (key && key.ctrl && key.name === 'c')
) {
if (!ctx.isCancelling) {
ctx.logger.logUpdate.clear()
ctx.logger.log(
c.red('Cancelling test run. Press CTRL+c again to exit forcefully.\n'),
)

View File

@ -71,6 +71,7 @@ async function runBenchmarkSuite(suite: Suite, runner: NodeBenchmarkRunner) {
const task = e.task
const taskRes = task.result!
const result = benchmark.result!.benchmark!
benchmark.result!.state = 'pass'
Object.assign(result, taskRes)
// compute extra stats and free raw samples as early as possible
const samples = result.samples
@ -114,13 +115,14 @@ async function runBenchmarkSuite(suite: Suite, runner: NodeBenchmarkRunner) {
const task = new Task(benchmarkInstance, benchmark.name, benchmarkFn)
benchmarkTasks.set(benchmark, task)
addBenchTaskListener(task, benchmark)
updateTask(benchmark)
})
const { setTimeout } = getSafeTimers()
const tasks: [BenchTask, Benchmark][] = []
for (const benchmark of benchmarkGroup) {
const task = benchmarkTasks.get(benchmark)!
updateTask(benchmark)
await task.warmup()
tasks.push([
await new Promise<BenchTask>(resolve =>
@ -135,16 +137,6 @@ async function runBenchmarkSuite(suite: Suite, runner: NodeBenchmarkRunner) {
suite.result!.duration = performance.now() - start
suite.result!.state = 'pass'
tasks
.sort(([taskA], [taskB]) => taskA.result!.mean - taskB.result!.mean)
.forEach(([, benchmark], idx) => {
benchmark.result!.state = 'pass'
if (benchmark) {
const result = benchmark.result!.benchmark!
result.rank = Number(idx) + 1
updateTask(benchmark)
}
})
updateTask(suite)
defer.resolve(null)

94
pnpm-lock.yaml generated
View File

@ -965,9 +965,6 @@ importers:
chai-subset:
specifier: ^1.6.0
version: 1.6.0
cli-truncate:
specifier: ^4.0.0
version: 4.0.0
fast-glob:
specifier: 3.3.2
version: 3.3.2
@ -989,9 +986,6 @@ importers:
local-pkg:
specifier: ^0.5.1
version: 0.5.1
log-update:
specifier: ^5.0.1
version: 5.0.1
micromatch:
specifier: ^4.0.8
version: 4.0.8
@ -4443,10 +4437,6 @@ packages:
resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
engines: {node: '>=8'}
ansi-escapes@5.0.0:
resolution: {integrity: sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==}
engines: {node: '>=12'}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
@ -4902,18 +4892,10 @@ packages:
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
engines: {node: '>=8'}
cli-cursor@4.0.0:
resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
cli-spinners@2.7.0:
resolution: {integrity: sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==}
engines: {node: '>=6'}
cli-truncate@4.0.0:
resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==}
engines: {node: '>=18'}
cli-width@4.0.0:
resolution: {integrity: sha512-ZksGS2xpa/bYkNzN3BAw1wEjsLV/ZKOf/CCrJ/QOBsxx6fOARIkwTutxp1XIOIohi6HKmOFjMoK/XaqDVUpEEw==}
engines: {node: '>= 12'}
@ -5485,9 +5467,6 @@ packages:
emoji-regex-xs@1.0.0:
resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==}
emoji-regex@10.3.0:
resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@ -6128,10 +6107,6 @@ packages:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
get-east-asian-width@1.3.0:
resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==}
engines: {node: '>=18'}
get-intrinsic@1.2.4:
resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
engines: {node: '>= 0.4'}
@ -6577,10 +6552,6 @@ packages:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
is-fullwidth-code-point@4.0.0:
resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==}
engines: {node: '>=12'}
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
@ -7004,10 +6975,6 @@ packages:
resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
engines: {node: '>=10'}
log-update@5.0.1:
resolution: {integrity: sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
loglevel-plugin-prefix@0.8.4:
resolution: {integrity: sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==}
@ -8133,10 +8100,6 @@ packages:
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
engines: {node: '>=8'}
restore-cursor@4.0.0:
resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
ret@0.2.2:
resolution: {integrity: sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==}
engines: {node: '>=4'}
@ -8411,10 +8374,6 @@ packages:
slashes@3.0.12:
resolution: {integrity: sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==}
slice-ansi@5.0.0:
resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==}
engines: {node: '>=12'}
smart-buffer@4.2.0:
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
@ -8554,10 +8513,6 @@ packages:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'}
string-width@7.0.0:
resolution: {integrity: sha512-GPQHj7row82Hjo9hKZieKcHIhaAIKOJvFSIZXuCU9OASVZrMNUaZuz++SPVrBjnLsnk4k+z9f2EIypgxf2vNFw==}
engines: {node: '>=18'}
string.prototype.matchall@4.0.11:
resolution: {integrity: sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==}
engines: {node: '>= 0.4'}
@ -8956,10 +8911,6 @@ packages:
resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==}
engines: {node: '>=8'}
type-fest@1.4.0:
resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==}
engines: {node: '>=10'}
type-fest@2.13.0:
resolution: {integrity: sha512-lPfAm42MxE4/456+QyIaaVBAwgpJb6xZ8PRu09utnhPdWwcyj9vgy6Sq0Z5yNbJ21EdxB5dRU/Qg8bsyAMtlcw==}
engines: {node: '>=12.20'}
@ -13153,10 +13104,6 @@ snapshots:
dependencies:
type-fest: 0.21.3
ansi-escapes@5.0.0:
dependencies:
type-fest: 1.4.0
ansi-regex@5.0.1: {}
ansi-regex@6.0.1: {}
@ -13723,17 +13670,8 @@ snapshots:
dependencies:
restore-cursor: 3.1.0
cli-cursor@4.0.0:
dependencies:
restore-cursor: 4.0.0
cli-spinners@2.7.0: {}
cli-truncate@4.0.0:
dependencies:
slice-ansi: 5.0.0
string-width: 7.0.0
cli-width@4.0.0: {}
cli-width@4.1.0: {}
@ -14286,8 +14224,6 @@ snapshots:
emoji-regex-xs@1.0.0: {}
emoji-regex@10.3.0: {}
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
@ -15282,8 +15218,6 @@ snapshots:
get-caller-file@2.0.5: {}
get-east-asian-width@1.3.0: {}
get-intrinsic@1.2.4:
dependencies:
es-errors: 1.3.0
@ -15816,8 +15750,6 @@ snapshots:
is-fullwidth-code-point@3.0.0: {}
is-fullwidth-code-point@4.0.0: {}
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
@ -16274,14 +16206,6 @@ snapshots:
chalk: 4.1.2
is-unicode-supported: 0.1.0
log-update@5.0.1:
dependencies:
ansi-escapes: 5.0.0
cli-cursor: 4.0.0
slice-ansi: 5.0.0
strip-ansi: 7.1.0
wrap-ansi: 8.1.0
loglevel-plugin-prefix@0.8.4: {}
loglevel@1.8.1: {}
@ -17585,11 +17509,6 @@ snapshots:
onetime: 5.1.2
signal-exit: 3.0.7
restore-cursor@4.0.0:
dependencies:
onetime: 5.1.2
signal-exit: 3.0.7
ret@0.2.2: {}
reusify@1.0.4: {}
@ -17935,11 +17854,6 @@ snapshots:
slashes@3.0.12: {}
slice-ansi@5.0.0:
dependencies:
ansi-styles: 6.2.1
is-fullwidth-code-point: 4.0.0
smart-buffer@4.2.0: {}
smob@1.5.0: {}
@ -18096,12 +18010,6 @@ snapshots:
emoji-regex: 9.2.2
strip-ansi: 7.1.0
string-width@7.0.0:
dependencies:
emoji-regex: 10.3.0
get-east-asian-width: 1.3.0
strip-ansi: 7.1.0
string.prototype.matchall@4.0.11:
dependencies:
call-bind: 1.0.7
@ -18512,8 +18420,6 @@ snapshots:
type-fest@0.8.1: {}
type-fest@1.4.0: {}
type-fest@2.13.0: {}
type-fest@2.19.0: {}

View File

@ -0,0 +1,12 @@
import { bench, describe } from 'vitest'
import { setTimeout } from 'node:timers/promises'
const options = { iterations: 1, warmupIterations: 1 }
bench('first', async () => {
await setTimeout(500)
}, options)
bench('second', async () => {
await setTimeout(500)
}, options)

View File

@ -1,4 +1,4 @@
import type { FormattedBenchmarkReport } from 'vitest/src/node/reporters/benchmark/table/index.js'
import type { createBenchmarkJsonReport } from 'vitest/src/node/reporters/benchmark/json-formatter.js'
import fs from 'node:fs'
import * as pathe from 'pathe'
import { expect, it } from 'vitest'
@ -17,10 +17,11 @@ it('basic', { timeout: 60_000 }, async () => {
// Verify that type testing cannot be used with benchmark
typecheck: { enabled: true },
}, [], 'benchmark')
expect(result.stderr).toBe('')
expect(result.exitCode).toBe(0)
const benchResult = await fs.promises.readFile(benchFile, 'utf-8')
const resultJson: FormattedBenchmarkReport = JSON.parse(benchResult)
const resultJson: ReturnType<typeof createBenchmarkJsonReport> = JSON.parse(benchResult)
const names = resultJson.files.map(f => f.groups.map(g => [g.fullName, g.benchmarks.map(b => b.name)]))
expect(names).toMatchInlineSnapshot(`
[

View File

@ -24,7 +24,7 @@ test('compare', { timeout: 60_000 }, async () => {
reporters: ['default'],
}, [], 'benchmark')
expect(result.exitCode).toBe(0)
const lines = result.stdout.split('\n').slice(3).slice(0, 6)
const lines = result.stdout.split('\n').slice(4).slice(0, 6)
const expected = `
basic.bench.ts > suite
name
@ -33,6 +33,9 @@ test('compare', { timeout: 60_000 }, async () => {
· sleep100
(baseline)
`
expect(lines).toMatchObject(expected.trim().split('\n').map(s => expect.stringContaining(s.trim())))
for (const [index, line] of expected.trim().split('\n').entries()) {
expect(lines[index]).toMatch(line.trim())
}
}
})

View File

@ -1,5 +1,7 @@
import type { RunnerTestCase } from 'vitest'
import * as pathe from 'pathe'
import { assert, expect, it } from 'vitest'
import { TaskParser } from 'vitest/src/node/reporters/task-parser.js'
import { runVitest } from '../../test-utils'
it('summary', async () => {
@ -13,12 +15,13 @@ it('summary', async () => {
it('non-tty', async () => {
const root = pathe.join(import.meta.dirname, '../fixtures/basic')
const result = await runVitest({ root }, ['base.bench.ts'], 'benchmark')
const lines = result.stdout.split('\n').slice(3).slice(0, 10)
const lines = result.stdout.split('\n').slice(4).slice(0, 11)
const expected = `\
base.bench.ts > sort
name
· normal
· reverse
base.bench.ts > timeout
name
· timeout100
@ -26,7 +29,34 @@ it('non-tty', async () => {
· timeout50
· timeout25
`
expect(lines).toMatchObject(expected.trim().split('\n').map(s => expect.stringContaining(s)))
for (const [index, line] of expected.trim().split('\n').entries()) {
expect(lines[index]).toMatch(line)
}
})
it('reports passed tasks just once', async () => {
const passed: string[] = []
class CustomReporter extends TaskParser {
onTestFinished(_test: RunnerTestCase): void {
passed.push(_test.name)
}
}
await runVitest({
root: pathe.join(import.meta.dirname, '../fixtures/reporter'),
benchmark: {
reporters: new CustomReporter(),
},
}, ['multiple.bench.ts'], 'benchmark')
expect(passed).toMatchInlineSnapshot(`
[
"first",
"second",
]
`)
})
it.for([true, false])('includeSamples %s', async (includeSamples) => {

View File

@ -1,10 +1,21 @@
import { expect, test } from 'vitest'
import { runVitest } from '../../test-utils'
const IS_PLAYWRIGHT = process.env.PROVIDER === 'playwright'
test('benchmark', async () => {
const result = await runVitest({ root: 'fixtures/benchmark' }, [], 'benchmark')
expect(result.stderr).toReportNoErrors()
// TODO 2024-12-11 check |name| when it's supported
expect(result.stdout).toContain('✓ basic.bench.ts > suite-a')
if (IS_PLAYWRIGHT) {
expect(result.stdout).toContain('✓ |chromium| basic.bench.ts > suite-a')
expect(result.stdout).toContain('✓ |firefox| basic.bench.ts > suite-a')
expect(result.stdout).toContain('✓ |webkit| basic.bench.ts > suite-a')
}
else {
expect(result.stdout).toContain('✓ |chrome| basic.bench.ts > suite-a')
expect(result.stdout).toContain('✓ |firefox| basic.bench.ts > suite-a')
}
expect(result.exitCode).toBe(0)
})

View File

@ -8,6 +8,7 @@ export default defineConfig({
singleFork: true,
},
},
reporters: 'verbose',
setupFiles: ['./setup.unit.ts'],
// 3 is the maximum of browser instances - in a perfect world they will run in parallel
hookTimeout: process.env.CI ? 120_000 * 3 : 20_000,

View File

@ -1,32 +1,31 @@
import { x } from 'tinyexec'
import { expect, test } from 'vitest'
// use "tinyexec" directly since "runVitestCli" strips color
import { runVitest } from '../../test-utils'
test('with color', async () => {
const proc = await x('vitest', ['run', '--root=./fixtures/console-color'], {
nodeOptions: {
env: {
CI: '1',
FORCE_COLOR: '1',
NO_COLOR: undefined,
GITHUB_ACTIONS: undefined,
},
const { stdout } = await runVitest({
root: 'fixtures/console-color',
env: {
CI: '1',
FORCE_COLOR: '1',
NO_COLOR: undefined,
GITHUB_ACTIONS: undefined,
},
})
expect(proc.stdout).toContain('\x1B[33mtrue\x1B[39m\n')
}, undefined, undefined, undefined, { preserveAnsi: true })
expect(stdout).toContain('\x1B[33mtrue\x1B[39m\n')
})
test('without color', async () => {
const proc = await x('vitest', ['run', '--root=./fixtures/console-color'], {
nodeOptions: {
env: {
CI: '1',
FORCE_COLOR: undefined,
NO_COLOR: '1',
GITHUB_ACTIONS: undefined,
},
const { stdout } = await runVitest({
root: 'fixtures/console-color',
env: {
CI: '1',
FORCE_COLOR: undefined,
NO_COLOR: '1',
GITHUB_ACTIONS: undefined,
},
})
expect(proc.stdout).toContain('true\n')
}, undefined, undefined, undefined, { preserveAnsi: true })
expect(stdout).toContain('true\n')
expect(stdout).not.toContain('\x1B[33mtrue\x1B[39m\n')
})

View File

@ -1,4 +1,4 @@
import { x } from 'tinyexec'
import { spawnSync } from 'node:child_process'
import { expect, test } from 'vitest'
import { runVitest } from '../../test-utils'
@ -35,20 +35,19 @@ test.each([
})
test('should not pass execArgv to workers when not specified in the config', async () => {
const { stdout, stderr } = await x('node', [
const { stdout, stderr } = spawnSync('node', [
'--title',
'this-works-only-on-main-thread',
'../../../../node_modules/vitest/vitest.mjs',
'--run',
], {
nodeOptions: {
cwd: `${process.cwd()}/fixtures/no-exec-args-fixtures`,
env: {
VITE_NODE_DEPS_MODULE_DIRECTORIES: '/node_modules/,/packages/',
NO_COLOR: '1',
},
encoding: 'utf-8',
cwd: `${process.cwd()}/fixtures/no-exec-args-fixtures`,
env: {
...process.env,
VITE_NODE_DEPS_MODULE_DIRECTORIES: '/node_modules/,/packages/',
NO_COLOR: '1',
},
throwOnError: false,
})
expect(stderr).not.toContain('Error: Initiated Worker with invalid execArgv flags: --title')
@ -57,21 +56,20 @@ test('should not pass execArgv to workers when not specified in the config', asy
})
test('should let allowed args pass to workers', async () => {
const { stdout, stderr } = await x('node', [
const { stdout, stderr } = spawnSync('node', [
'--heap-prof',
'--diagnostic-dir=/tmp/vitest-diagnostics',
'--heap-prof-name=heap.prof',
'../../../../node_modules/vitest/vitest.mjs',
'--run',
], {
nodeOptions: {
cwd: `${process.cwd()}/fixtures/allowed-exec-args-fixtures`,
env: {
VITE_NODE_DEPS_MODULE_DIRECTORIES: '/node_modules/,/packages/',
NO_COLOR: '1',
},
encoding: 'utf-8',
cwd: `${process.cwd()}/fixtures/allowed-exec-args-fixtures`,
env: {
...process.env,
VITE_NODE_DEPS_MODULE_DIRECTORIES: '/node_modules/,/packages/',
NO_COLOR: '1',
},
throwOnError: false,
})
expect(stderr).toBe('')

View File

@ -12,9 +12,12 @@ export class Cli {
private stdoutListeners: Listener[] = []
private stderrListeners: Listener[] = []
private stdin: ReadableOrWritable
private preserveAnsi?: boolean
constructor(options: { stdin: ReadableOrWritable; stdout: ReadableOrWritable; stderr: ReadableOrWritable }) {
constructor(options: { stdin: ReadableOrWritable; stdout: ReadableOrWritable; stderr: ReadableOrWritable; preserveAnsi?: boolean }) {
this.stdin = options.stdin
this.stdin = options.stdin
this.preserveAnsi = options.preserveAnsi
for (const source of (['stdout', 'stderr'] as const)) {
const stream = options[source]
@ -37,7 +40,7 @@ export class Cli {
}
private capture(source: Source, data: any) {
const msg = stripVTControlCharacters(data.toString())
const msg = this.preserveAnsi ? data.toString() : stripVTControlCharacters(data.toString())
this[source] += msg
this[`${source}Listeners`].forEach(fn => fn())
}

View File

@ -20,6 +20,7 @@ Object.assign(tinyrainbow.default, tinyrainbow.getDefaultColors())
interface VitestRunnerCLIOptions {
std?: 'inherit'
fails?: boolean
preserveAnsi?: boolean
}
export async function runVitest(
@ -58,7 +59,7 @@ export async function runVitest(
const stdin = new Readable({ read: () => '' }) as NodeJS.ReadStream
stdin.isTTY = true
stdin.setRawMode = () => stdin
const cli = new Cli({ stdin, stdout, stderr })
const cli = new Cli({ stdin, stdout, stderr, preserveAnsi: runnerOptions.preserveAnsi })
let ctx: Vitest | undefined
let thrown = false