mirror of
https://github.com/vitest-dev/vitest.git
synced 2025-12-08 18:26:03 +00:00
104 lines
2.9 KiB
TypeScript
104 lines
2.9 KiB
TypeScript
import type { Readable, Writable } from 'node:stream'
|
|
import { stripVTControlCharacters } from 'node:util'
|
|
|
|
type Listener = (() => void)
|
|
type ReadableOrWritable = Readable | Writable
|
|
type Source = 'stdout' | 'stderr'
|
|
|
|
export class Cli {
|
|
stdout = ''
|
|
stderr = ''
|
|
|
|
private stdoutListeners: Listener[] = []
|
|
private stderrListeners: Listener[] = []
|
|
private stdin: ReadableOrWritable
|
|
private preserveAnsi?: boolean
|
|
|
|
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]
|
|
|
|
if ((stream as Readable).readable) {
|
|
stream.on('data', (data) => {
|
|
this.capture(source, data)
|
|
})
|
|
}
|
|
else if (isWritable(stream)) {
|
|
const original = stream.write.bind(stream)
|
|
|
|
// @ts-expect-error -- Is there a better way to detect when a Writable is being written into?
|
|
stream.write = (data, encoding, callback) => {
|
|
this.capture(source, data)
|
|
return original(data, encoding, callback)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private capture(source: Source, data: any) {
|
|
const msg = this.preserveAnsi ? data.toString() : stripVTControlCharacters(data.toString())
|
|
this[source] += msg
|
|
this[`${source}Listeners`].forEach(fn => fn())
|
|
}
|
|
|
|
write(data: string) {
|
|
this.resetOutput()
|
|
|
|
if (((this.stdin as Readable).readable)) {
|
|
this.stdin.emit('data', data)
|
|
}
|
|
else if (isWritable(this.stdin)) {
|
|
this.stdin.write(data)
|
|
}
|
|
}
|
|
|
|
resetOutput() {
|
|
this.stdout = ''
|
|
this.stderr = ''
|
|
}
|
|
|
|
waitForStdout(expected: string) {
|
|
return this.waitForOutput(expected, 'stdout', this.waitForStdout)
|
|
}
|
|
|
|
waitForStderr(expected: string) {
|
|
return this.waitForOutput(expected, 'stderr', this.waitForStderr)
|
|
}
|
|
|
|
private waitForOutput(expected: string, source: Source, caller: Parameters<typeof Error.captureStackTrace>[1]) {
|
|
const error = new Error('Timeout')
|
|
Error.captureStackTrace(error, caller)
|
|
|
|
return new Promise<void>((resolve, reject) => {
|
|
if (this[source].includes(expected)) {
|
|
return resolve()
|
|
}
|
|
|
|
const timeout = setTimeout(() => {
|
|
error.message = `Timeout when waiting for error "${expected}".\nReceived:\nstdout: ${this.stdout}\nstderr: ${this.stderr}`
|
|
reject(error)
|
|
}, process.env.CI ? 20_000 : 4_000)
|
|
|
|
const listener = () => {
|
|
if (this[source].includes(expected)) {
|
|
if (timeout) {
|
|
clearTimeout(timeout)
|
|
}
|
|
|
|
resolve()
|
|
}
|
|
}
|
|
|
|
this[`${source}Listeners`].push(listener)
|
|
})
|
|
}
|
|
}
|
|
|
|
function isWritable(stream: any): stream is Writable {
|
|
return stream && typeof stream.write === 'function'
|
|
}
|