diff --git a/packages/vitest/LICENSE.md b/packages/vitest/LICENSE.md index 375bf139ecfe..d6f5ab060532 100644 --- a/packages/vitest/LICENSE.md +++ b/packages/vitest/LICENSE.md @@ -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 (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 (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 (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 (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 (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 -> -> 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 (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 (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 (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.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 (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 (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 -> Copyright (c) Sindre Sorhus (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 (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 (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 (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 diff --git a/packages/vitest/package.json b/packages/vitest/package.json index 67739ab72cfd..ee100e0b2a25 100644 --- a/packages/vitest/package.json +++ b/packages/vitest/package.json @@ -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", diff --git a/packages/vitest/src/node/core.ts b/packages/vitest/src/node/core.ts index 6be0b052ab39..c3b2d725cc0d 100644 --- a/packages/vitest/src/node/core.ts +++ b/packages/vitest/src/node/core.ts @@ -1113,7 +1113,6 @@ export class Vitest { this.logger.error('error during close', r.reason) } }) - this.logger.logUpdate.done() // restore terminal cursor }) })() } diff --git a/packages/vitest/src/node/error.ts b/packages/vitest/src/node/error.ts index 44de1ea400db..20fc2f64106f 100644 --- a/packages/vitest/src/node/error.ts +++ b/packages/vitest/src/node/error.ts @@ -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) { diff --git a/packages/vitest/src/node/logger.ts b/packages/vitest/src/node/logger.ts index 490b774be044..070d6c72d055 100644 --- a/packages/vitest/src/node/logger.ts +++ b/packages/vitest/src/node/logger.ts @@ -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 - private _clearScreenPending: string | undefined private _highlights = new Map() + 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 diff --git a/packages/vitest/src/node/reporters/base.ts b/packages/vitest/src/node/reporters/base.ts index c31b30a29c9d..6ee4ef1347fe 100644 --- a/packages/vitest/src/node/reporters/base.ts +++ b/packages/vitest/src/node/reporters/base.ts @@ -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) diff --git a/packages/vitest/src/node/reporters/benchmark/index.ts b/packages/vitest/src/node/reporters/benchmark/index.ts index 554c5d1998cf..e82fb37cdb5d 100644 --- a/packages/vitest/src/node/reporters/benchmark/index.ts +++ b/packages/vitest/src/node/reporters/benchmark/index.ts @@ -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 diff --git a/packages/vitest/src/node/reporters/benchmark/json-formatter.ts b/packages/vitest/src/node/reporters/benchmark/json-formatter.ts new file mode 100644 index 000000000000..517d29216e78 --- /dev/null +++ b/packages/vitest/src/node/reporters/benchmark/json-formatter.ts @@ -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 = {} + + for (const file of report.files) { + for (const group of file.groups) { + for (const t of group.benchmarks) { + flat[t.id] = t + } + } + } + + return flat +} diff --git a/packages/vitest/src/node/reporters/benchmark/reporter.ts b/packages/vitest/src/node/reporters/benchmark/reporter.ts new file mode 100644 index 000000000000..43a4628ba540 --- /dev/null +++ b/packages/vitest/src/node/reporters/benchmark/reporter.ts @@ -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[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}`) + } + } +} diff --git a/packages/vitest/src/node/reporters/benchmark/table/index.ts b/packages/vitest/src/node/reporters/benchmark/table/index.ts deleted file mode 100644 index f7ab74f3422d..000000000000 --- a/packages/vitest/src/node/reporters/benchmark/table/index.ts +++ /dev/null @@ -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 - 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 -} diff --git a/packages/vitest/src/node/reporters/benchmark/table/tableRender.ts b/packages/vitest/src/node/reporters/benchmark/tableRender.ts similarity index 55% rename from packages/vitest/src/node/reporters/benchmark/table/tableRender.ts rename to packages/vitest/src/node/reporters/benchmark/tableRender.ts index f3cd66bc2094..646d48db5e93 100644 --- a/packages/vitest/src/node/reporters/benchmark/table/tableRender.ts +++ b/packages/vitest/src/node/reporters/benchmark/tableRender.ts @@ -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() -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 + }, ): 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 = {} + + 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() - }, - } -} diff --git a/packages/vitest/src/node/reporters/benchmark/verbose.ts b/packages/vitest/src/node/reporters/benchmark/verbose.ts new file mode 100644 index 000000000000..b3b20c91ccfa --- /dev/null +++ b/packages/vitest/src/node/reporters/benchmark/verbose.ts @@ -0,0 +1,5 @@ +import { BenchmarkReporter } from './reporter' + +export class VerboseBenchmarkReporter extends BenchmarkReporter { + protected verbose = true +} diff --git a/packages/vitest/src/node/reporters/dot.ts b/packages/vitest/src/node/reporters/dot.ts index 718c81a326cd..96ab171d1a89 100644 --- a/packages/vitest/src/node/reporters/dot.ts +++ b/packages/vitest/src/node/reporters/dot.ts @@ -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 diff --git a/packages/vitest/src/node/reporters/renderers/utils.ts b/packages/vitest/src/node/reporters/renderers/utils.ts index a8f057dba45e..25893706f1ef 100644 --- a/packages/vitest/src/node/reporters/renderers/utils.ts +++ b/packages/vitest/src/node/reporters/renderers/utils.ts @@ -14,18 +14,15 @@ import { F_POINTER, } from './figures' -export const spinnerMap = new WeakMap string>() -export const hookSpinnerMap = new WeakMap 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)}…` +} diff --git a/packages/vitest/src/node/reporters/renderers/windowedRenderer.ts b/packages/vitest/src/node/reporters/renderers/windowedRenderer.ts index 5f836979d51d..742d4a53279b 100644 --- a/packages/vitest/src/node/reporters/renderers/windowedRenderer.ts +++ b/packages/vitest/src/node/reporters/renderers/windowedRenderer.ts @@ -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` */ diff --git a/packages/vitest/src/node/reporters/verbose.ts b/packages/vitest/src/node/reporters/verbose.ts index 8b2c03f6a974..b4449685eb98 100644 --- a/packages/vitest/src/node/reporters/verbose.ts +++ b/packages/vitest/src/node/reporters/verbose.ts @@ -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}`))) } } } diff --git a/packages/vitest/src/node/stdin.ts b/packages/vitest/src/node/stdin.ts index 291cbe13119e..e12d397104ca 100644 --- a/packages/vitest/src/node/stdin.ts +++ b/packages/vitest/src/node/stdin.ts @@ -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'), ) diff --git a/packages/vitest/src/runtime/runners/benchmark.ts b/packages/vitest/src/runtime/runners/benchmark.ts index 59aa7ac7dc64..c24e5d596ab8 100644 --- a/packages/vitest/src/runtime/runners/benchmark.ts +++ b/packages/vitest/src/runtime/runners/benchmark.ts @@ -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(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) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7f066c8088d..60b43f505842 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: {} diff --git a/test/benchmark/fixtures/reporter/multiple.bench.ts b/test/benchmark/fixtures/reporter/multiple.bench.ts new file mode 100644 index 000000000000..1b204ffa7a96 --- /dev/null +++ b/test/benchmark/fixtures/reporter/multiple.bench.ts @@ -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) diff --git a/test/benchmark/test/basic.test.ts b/test/benchmark/test/basic.test.ts index b0cb1e0f72ec..86b4eee0a6da 100644 --- a/test/benchmark/test/basic.test.ts +++ b/test/benchmark/test/basic.test.ts @@ -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 = JSON.parse(benchResult) const names = resultJson.files.map(f => f.groups.map(g => [g.fullName, g.benchmarks.map(b => b.name)])) expect(names).toMatchInlineSnapshot(` [ diff --git a/test/benchmark/test/compare.test.ts b/test/benchmark/test/compare.test.ts index 5e48a4065592..32bff227fdad 100644 --- a/test/benchmark/test/compare.test.ts +++ b/test/benchmark/test/compare.test.ts @@ -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()) + } } }) diff --git a/test/benchmark/test/reporter.test.ts b/test/benchmark/test/reporter.test.ts index d118732ff71d..4e3895ede04e 100644 --- a/test/benchmark/test/reporter.test.ts +++ b/test/benchmark/test/reporter.test.ts @@ -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) => { diff --git a/test/browser/specs/benchmark.test.ts b/test/browser/specs/benchmark.test.ts index 1b3eb94b5d7c..ed3d7dd2e1a2 100644 --- a/test/browser/specs/benchmark.test.ts +++ b/test/browser/specs/benchmark.test.ts @@ -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) }) diff --git a/test/browser/vitest.config.unit.mts b/test/browser/vitest.config.unit.mts index 8bcb174b0ae8..e217ab15ccd4 100644 --- a/test/browser/vitest.config.unit.mts +++ b/test/browser/vitest.config.unit.mts @@ -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, diff --git a/test/config/test/console-color.test.ts b/test/config/test/console-color.test.ts index 1678a55cacfe..e139f69aff6d 100644 --- a/test/config/test/console-color.test.ts +++ b/test/config/test/console-color.test.ts @@ -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') }) diff --git a/test/config/test/exec-args.test.ts b/test/config/test/exec-args.test.ts index 40ccb73cc780..9e68d6c4406d 100644 --- a/test/config/test/exec-args.test.ts +++ b/test/config/test/exec-args.test.ts @@ -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('') diff --git a/test/test-utils/cli.ts b/test/test-utils/cli.ts index e7d8b4f359dd..3db845324ef4 100644 --- a/test/test-utils/cli.ts +++ b/test/test-utils/cli.ts @@ -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()) } diff --git a/test/test-utils/index.ts b/test/test-utils/index.ts index 85b4cbf4c0f2..92bca066d713 100644 --- a/test/test-utils/index.ts +++ b/test/test-utils/index.ts @@ -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