Skip to content

Commit

Permalink
feat(api): startVitest() to accept stdout and stdin (#5493)
Browse files Browse the repository at this point in the history
  • Loading branch information
AriPerkkio committed Apr 23, 2024
1 parent 23bd3cd commit 780b187
Show file tree
Hide file tree
Showing 34 changed files with 375 additions and 350 deletions.
2 changes: 2 additions & 0 deletions eslint.config.js
Expand Up @@ -13,8 +13,10 @@ export default antfu(
'**/assets/**',
'**/*.timestamp-*',
'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
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
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
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
@@ -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
@@ -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
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
@@ -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
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
@@ -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
@@ -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
Expand Up @@ -308,6 +308,7 @@ export class WorkspaceProject {
coverage: this.ctx.config.coverage,
},
server.config,
this.ctx.logger,
)

this.server = server
Expand Down

0 comments on commit 780b187

Please sign in to comment.