Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): startVitest() to accept stdout and stdin #5493

Merged
merged 4 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ export default antfu(
'**/bench.json',
'**/fixtures',
'test/core/src/self',
'test/cache/cache/.vitest-base/results.json',
'test/wasm-modules/src/wasm-bindgen-no-cyclic',
'test/workspaces/results.json',
'test/workspaces-browser/results.json',
'test/reporters/fixtures/with-syntax-error.test.js',
'test/network-imports/public/slash@3.0.0.js',
'test/coverage-test/src/transpiled.js',
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const external = [
'worker_threads',
'node:worker_threads',
'node:fs',
'node:stream',
'node:vm',
'inspector',
'vite-node/source-map',
Expand Down
6 changes: 4 additions & 2 deletions packages/vitest/src/node/cli/cli-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,11 @@ export async function startVitest(
return ctx
}

const stdin = vitestOptions?.stdin || process.stdin
const stdout = vitestOptions?.stdout || process.stdout
let stdinCleanup
if (process.stdin.isTTY && ctx.config.watch)
stdinCleanup = registerConsoleShortcuts(ctx)
if (stdin.isTTY && ctx.config.watch)
stdinCleanup = registerConsoleShortcuts(ctx, stdin, stdout)

ctx.onServerRestart((reason) => {
ctx.report('onServerRestart', reason)
Expand Down
12 changes: 7 additions & 5 deletions packages/vitest/src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { BaseSequencer } from './sequencers/BaseSequencer'
import { RandomSequencer } from './sequencers/RandomSequencer'
import type { BenchmarkBuiltinReporters } from './reporters'
import { builtinPools } from './pool'
import type { Logger } from './logger'

function resolvePath(path: string, root: string) {
return normalize(
Expand Down Expand Up @@ -76,13 +77,14 @@ export function resolveConfig(
mode: VitestRunMode,
options: UserConfig,
viteConfig: ResolvedViteConfig,
logger: Logger,
): ResolvedConfig {
if (options.dom) {
if (
viteConfig.test?.environment != null
&& viteConfig.test!.environment !== 'happy-dom'
) {
console.warn(
logger.console.warn(
c.yellow(
`${c.inverse(c.yellow(' Vitest '))} Your config.test.environment ("${
viteConfig.test.environment
Expand Down Expand Up @@ -209,11 +211,11 @@ export function resolveConfig(
return

if (option === 'fallbackCJS') {
console.warn(c.yellow(`${c.inverse(c.yellow(' Vitest '))} "deps.${option}" is deprecated. Use "server.deps.${option}" instead`))
logger.console.warn(c.yellow(`${c.inverse(c.yellow(' Vitest '))} "deps.${option}" is deprecated. Use "server.deps.${option}" instead`))
}
else {
const transformMode = resolved.environment === 'happy-dom' || resolved.environment === 'jsdom' ? 'web' : 'ssr'
console.warn(
logger.console.warn(
c.yellow(
`${c.inverse(c.yellow(' Vitest '))} "deps.${option}" is deprecated. If you rely on vite-node directly, use "server.deps.${option}" instead. Otherwise, consider using "deps.optimizer.${transformMode}.${option === 'external' ? 'exclude' : 'include'}"`,
),
Expand Down Expand Up @@ -475,7 +477,7 @@ export function resolveConfig(
let cacheDir = VitestCache.resolveCacheDir('', resolve(viteConfig.cacheDir, 'vitest'), resolved.name)

if (resolved.cache && resolved.cache.dir) {
console.warn(
logger.console.warn(
c.yellow(
`${c.inverse(c.yellow(' Vitest '))} "cache.dir" is deprecated, use Vite's "cacheDir" instead if you want to change the cache director. Note caches will be written to "cacheDir\/vitest"`,
),
Expand Down Expand Up @@ -514,7 +516,7 @@ export function resolveConfig(
resolved.typecheck.enabled ??= false

if (resolved.typecheck.enabled)
console.warn(c.yellow('Testing types with tsc and vue-tsc is an experimental feature.\nBreaking changes might not follow SemVer, please pin Vitest\'s version when using it.'))
logger.console.warn(c.yellow('Testing types with tsc and vue-tsc is an experimental feature.\nBreaking changes might not follow SemVer, please pin Vitest\'s version when using it.'))

resolved.browser ??= {} as any
resolved.browser.enabled ??= false
Expand Down
8 changes: 6 additions & 2 deletions packages/vitest/src/node/core.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { existsSync, promises as fs } from 'node:fs'
import type { Writable } from 'node:stream'
import { isMainThread } from 'node:worker_threads'
import type { ViteDevServer } from 'vite'
import { mergeConfig } from 'vite'
Expand Down Expand Up @@ -31,6 +32,9 @@ const WATCHER_DEBOUNCE = 100

export interface VitestOptions {
packageInstaller?: VitestPackageInstaller
stdin?: NodeJS.ReadStream
stdout?: NodeJS.WriteStream | Writable
stderr?: NodeJS.WriteStream | Writable
}

export class Vitest {
Expand Down Expand Up @@ -74,7 +78,7 @@ export class Vitest {
public readonly mode: VitestRunMode,
options: VitestOptions = {},
) {
this.logger = new Logger(this)
this.logger = new Logger(this, options.stdout, options.stderr)
this.packageInstaller = options.packageInstaller || new VitestPackageInstaller()
}

Expand All @@ -93,7 +97,7 @@ export class Vitest {
this.runningPromise = undefined
this.projectsTestFiles.clear()

const resolved = resolveConfig(this.mode, options, server.config)
const resolved = resolveConfig(this.mode, options, server.config, this.logger)

this.server = server
this.config = resolved
Expand Down
12 changes: 8 additions & 4 deletions packages/vitest/src/node/logger.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Console } from 'node:console'
import type { Writable } from 'node:stream'
import { createLogUpdate } from 'log-update'
import c from 'picocolors'
import { version } from '../../../../package.json'
Expand All @@ -24,17 +26,19 @@ const CURSOR_TO_START = `${ESC}1;1H`
const CLEAR_SCREEN = '\x1Bc'

export class Logger {
outputStream = process.stdout
errorStream = process.stderr
logUpdate = createLogUpdate(process.stdout)
logUpdate: ReturnType<typeof createLogUpdate>

private _clearScreenPending: string | undefined
private _highlights = new Map<string, string>()
public console: Console

constructor(
public ctx: Vitest,
public console = globalThis.console,
public outputStream: NodeJS.WriteStream | Writable = process.stdout,
public errorStream: NodeJS.WriteStream | Writable = process.stderr,
) {
this.console = new Console({ stdout: outputStream, stderr: errorStream })
this.logUpdate = createLogUpdate(this.outputStream)
this._highlights.clear()
}

Expand Down
6 changes: 5 additions & 1 deletion packages/vitest/src/node/reporters/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,11 @@ export abstract class BaseReporter implements Reporter {
return
const task = log.taskId ? this.ctx.state.idMap.get(log.taskId) : undefined
const header = c.gray(log.type + c.dim(` | ${task ? getFullName(task, c.dim(' > ')) : log.taskId !== UNKNOWN_TEST_ID ? log.taskId : 'unknown test'}`))
process[log.type].write(`${header}\n${log.content}\n`)

const output = log.type === 'stdout' ? this.ctx.logger.outputStream : this.ctx.logger.errorStream

// @ts-expect-error -- write() method has different signature on the union type
output.write(`${header}\n${log.content}\n`)
}

shouldLog(log: UserConsoleLog) {
Expand Down
3 changes: 1 addition & 2 deletions packages/vitest/src/node/reporters/github-actions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Console } from 'node:console'
import { Writable } from 'node:stream'
import { getTasks } from '@vitest/runner/utils'
import stripAnsi from 'strip-ansi'
Expand Down Expand Up @@ -76,7 +75,7 @@ async function printErrorWrapper(error: unknown, ctx: Vitest, project: Workspace
})
const result = await printError(error, project, {
showCodeFrame: false,
logger: new Logger(ctx, new Console(writable, writable)),
logger: new Logger(ctx, writable, writable),
})
return { nearest: result?.nearest, output }
}
Expand Down
5 changes: 3 additions & 2 deletions packages/vitest/src/node/reporters/renderers/dotRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,10 @@ export function createDotRenderer(_tasks: Task[], options: DotRendererOptions) {
let timer: any

const { logUpdate: log, outputStream } = options.logger
const columns = 'columns' in outputStream ? outputStream.columns : 80

function update() {
log(render(tasks, outputStream.columns))
log(render(tasks, columns))
}

return {
Expand All @@ -114,7 +115,7 @@ export function createDotRenderer(_tasks: Task[], options: DotRendererOptions) {
timer = undefined
}
log.clear()
options.logger.log(render(tasks, outputStream.columns))
options.logger.log(render(tasks, columns))
return this
},
clear() {
Expand Down
23 changes: 12 additions & 11 deletions packages/vitest/src/node/stdin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import readline from 'node:readline'
import type { Writable } from 'node:stream'
import c from 'picocolors'
import prompt from 'prompts'
import { relative } from 'pathe'
Expand Down Expand Up @@ -28,7 +29,7 @@ ${keys.map(i => c.dim(' press ') + c.reset([i[0]].flat().map(c.bold).join(', ')
)
}

export function registerConsoleShortcuts(ctx: Vitest) {
export function registerConsoleShortcuts(ctx: Vitest, stdin: NodeJS.ReadStream = process.stdin, stdout: NodeJS.WriteStream | Writable) {
let latestFilename = ''

async function _keypressHandler(str: string, key: any) {
Expand Down Expand Up @@ -97,7 +98,7 @@ export function registerConsoleShortcuts(ctx: Vitest) {

async function inputNamePattern() {
off()
const watchFilter = new WatchFilter('Input test name pattern (RegExp)')
const watchFilter = new WatchFilter('Input test name pattern (RegExp)', stdin, stdout)
const filter = await watchFilter.filter((str: string) => {
const files = ctx.state.getFiles()
const tests = getTests(files)
Expand Down Expand Up @@ -130,7 +131,7 @@ export function registerConsoleShortcuts(ctx: Vitest) {
async function inputFilePattern() {
off()

const watchFilter = new WatchFilter('Input filename pattern')
const watchFilter = new WatchFilter('Input filename pattern', stdin, stdout)

const filter = await watchFilter.filter(async (str: string) => {
const files = await ctx.globTestFiles([str])
Expand All @@ -149,19 +150,19 @@ export function registerConsoleShortcuts(ctx: Vitest) {
let rl: readline.Interface | undefined
function on() {
off()
rl = readline.createInterface({ input: process.stdin, escapeCodeTimeout: 50 })
readline.emitKeypressEvents(process.stdin, rl)
if (process.stdin.isTTY)
process.stdin.setRawMode(true)
process.stdin.on('keypress', keypressHandler)
rl = readline.createInterface({ input: stdin, escapeCodeTimeout: 50 })
readline.emitKeypressEvents(stdin, rl)
if (stdin.isTTY)
stdin.setRawMode(true)
stdin.on('keypress', keypressHandler)
}

function off() {
rl?.close()
rl = undefined
process.stdin.removeListener('keypress', keypressHandler)
if (process.stdin.isTTY)
process.stdin.setRawMode(false)
stdin.removeListener('keypress', keypressHandler)
if (stdin.isTTY)
stdin.setRawMode(false)
}

on()
Expand Down
52 changes: 33 additions & 19 deletions packages/vitest/src/node/watch-filter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import readline from 'node:readline'
import type { Writable } from 'node:stream'
import c from 'picocolors'
import stripAnsi from 'strip-ansi'
import { createDefer } from '@vitest/utils'
import { stdout } from '../utils'
import { stdout as getStdout } from '../utils'

const MAX_RESULT_COUNT = 10
const SELECTION_MAX_INDEX = 7
Expand All @@ -17,24 +18,29 @@ export class WatchFilter {
private results: string[] = []
private selectionIndex = -1
private onKeyPress?: (str: string, key: any) => void
private stdin: NodeJS.ReadStream
private stdout: NodeJS.WriteStream | Writable

constructor(message: string) {
constructor(message: string, stdin: NodeJS.ReadStream = process.stdin, stdout: NodeJS.WriteStream | Writable = getStdout()) {
this.message = message
this.filterRL = readline.createInterface({ input: process.stdin, escapeCodeTimeout: 50 })
readline.emitKeypressEvents(process.stdin, this.filterRL)
if (process.stdin.isTTY)
process.stdin.setRawMode(true)
this.stdin = stdin
this.stdout = stdout

this.filterRL = readline.createInterface({ input: this.stdin, escapeCodeTimeout: 50 })
readline.emitKeypressEvents(this.stdin, this.filterRL)
if (this.stdin.isTTY)
this.stdin.setRawMode(true)
}

public async filter(filterFunc: FilterFunc): Promise<string | undefined> {
stdout().write(this.promptLine())
this.write(this.promptLine())

const resultPromise = createDefer<string | undefined>()

this.onKeyPress = this.filterHandler(filterFunc, (result) => {
resultPromise.resolve(result)
})
process.stdin.on('keypress', this.onKeyPress)
this.stdin.on('keypress', this.onKeyPress)
try {
return await resultPromise
}
Expand Down Expand Up @@ -138,31 +144,39 @@ export class WatchFilter {
private eraseAndPrint(str: string) {
let rows = 0
const lines = str.split(/\r?\n/)
for (const line of lines)
for (const line of lines) {
const columns = 'columns' in this.stdout ? this.stdout.columns : 80

// We have to take care of screen width in case of long lines
rows += 1 + Math.floor(Math.max(stripAnsi(line).length - 1, 0) / stdout().columns)
rows += 1 + Math.floor(Math.max(stripAnsi(line).length - 1, 0) / columns)
}

stdout().write(`${ESC}1G`) // move to the beginning of the line
stdout().write(`${ESC}J`) // erase down
stdout().write(str)
stdout().write(`${ESC}${rows - 1}A`) // moving up lines
this.write(`${ESC}1G`) // move to the beginning of the line
this.write(`${ESC}J`) // erase down
this.write(str)
this.write(`${ESC}${rows - 1}A`) // moving up lines
}

private close() {
this.filterRL.close()
if (this.onKeyPress)
process.stdin.removeListener('keypress', this.onKeyPress)
this.stdin.removeListener('keypress', this.onKeyPress)

if (process.stdin.isTTY)
process.stdin.setRawMode(false)
if (this.stdin.isTTY)
this.stdin.setRawMode(false)
}

private restoreCursor() {
const cursortPos = this.keywordOffset() + (this.currentKeyword?.length || 0)
stdout().write(`${ESC}${cursortPos}G`)
this.write(`${ESC}${cursortPos}G`)
}

private cancel() {
stdout().write(`${ESC}J`) // erase down
this.write(`${ESC}J`) // erase down
}

private write(data: string) {
// @ts-expect-error -- write() method has different signature on the union type
this.stdout.write(data)
}
}
1 change: 1 addition & 0 deletions packages/vitest/src/node/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ export class WorkspaceProject {
coverage: this.ctx.config.coverage,
},
server.config,
this.ctx.logger,
)

this.server = server
Expand Down