Skip to content

Commit

Permalink
fix(reporter)!: move ctx.log to ctx.logger.log, improve log flick…
Browse files Browse the repository at this point in the history
…ing (#1166)

Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
userquin and antfu committed Jul 9, 2022
1 parent 5b837f9 commit ffa3585
Show file tree
Hide file tree
Showing 22 changed files with 267 additions and 157 deletions.
4 changes: 2 additions & 2 deletions packages/vitest/src/node/cli-api.ts
Expand Up @@ -58,8 +58,8 @@ export async function startVitest(cliFilters: string[], options: CliOptions, vit
}
catch (e) {
process.exitCode = 1
await ctx.printError(e, true, 'Unhandled Error')
ctx.error('\n\n')
await ctx.logger.printError(e, true, 'Unhandled Error')
ctx.logger.error('\n\n')
return false
}

Expand Down
62 changes: 9 additions & 53 deletions packages/vitest/src/node/core.ts
@@ -1,5 +1,4 @@
import { existsSync, promises as fs } from 'fs'
import readline from 'readline'
import type { ViteDevServer } from 'vite'
import { relative, toNamespacedPath } from 'pathe'
import fg from 'fast-glob'
Expand All @@ -16,8 +15,8 @@ import type { WorkerPool } from './pool'
import { createReporters } from './reporters/utils'
import { StateManager } from './state'
import { resolveConfig } from './config'
import { printError } from './error'
import { VitestGit } from './git'
import { Logger } from './logger'
import { VitestCache } from './cache'

const WATCHER_DEBOUNCE = 100
Expand All @@ -32,12 +31,9 @@ export class Vitest {
snapshot: SnapshotManager = undefined!
cache: VitestCache = undefined!
reporters: Reporter[] = undefined!
console: Console
logger: Logger
pool: WorkerPool | undefined

outputStream = process.stdout
errorStream = process.stderr

vitenode: ViteNodeServer = undefined!

invalidates: Set<string> = new Set()
Expand All @@ -49,12 +45,12 @@ export class Vitest {
restartsCount = 0
runner: ViteNodeRunner = undefined!

private _onRestartListeners: Array<() => void> = []

constructor() {
this.console = globalThis.console
this.logger = new Logger(this)
}

private _onRestartListeners: Array<() => void> = []

async setServer(options: UserConfig, server: ViteDevServer) {
this.unregisterWatcher?.()
clearTimeout(this._rerunTimer)
Expand Down Expand Up @@ -100,7 +96,7 @@ export class Vitest {
await this.cache.results.readFromCache()
}
catch (err) {
this.error(`[vitest] Error, while trying to parse cache in ${this.cache.results.getCachePath()}:`, err)
this.logger.error(`[vitest] Error, while trying to parse cache in ${this.cache.results.getCachePath()}:`, err)
}
}

Expand Down Expand Up @@ -132,20 +128,7 @@ export class Vitest {
if (!files.length) {
const exitCode = this.config.passWithNoTests ? 0 : 1

const comma = c.dim(', ')
if (filters?.length)
this.console.error(c.dim('filter: ') + c.yellow(filters.join(comma)))
if (this.config.include)
this.console.error(c.dim('include: ') + c.yellow(this.config.include.join(comma)))
if (this.config.exclude)
this.console.error(c.dim('exclude: ') + c.yellow(this.config.exclude.join(comma)))
if (this.config.watchExclude)
this.console.error(c.dim('watch exclude: ') + c.yellow(this.config.watchExclude.join(comma)))

if (this.config.passWithNoTests)
this.log('No test files found, exiting with code 0\n')
else
this.error(c.red('\nNo test files found, exiting with code 1'))
this.logger.printNoTestFound(filters)

process.exit(exitCode)
}
Expand Down Expand Up @@ -193,7 +176,7 @@ export class Vitest {
changedSince: this.config.changed,
})
if (!related) {
this.error(c.red('Could not find Git root. Have you initialized git with `git init`?\n'))
this.logger.error(c.red('Could not find Git root. Have you initialized git with `git init`?\n'))
process.exit(1)
}
this.config.related = Array.from(new Set(related))
Expand Down Expand Up @@ -302,25 +285,6 @@ export class Vitest {
}
}

log(...args: any[]) {
this.console.log(...args)
}

error(...args: any[]) {
this.console.error(...args)
}

clearScreen() {
if (this.server.config.clearScreen === false)
return

const repeatCount = (process.stdout?.rows ?? 0) - 2
const blank = repeatCount > 0 ? '\n'.repeat(repeatCount) : ''
this.console.log(blank)
readline.cursorTo(process.stdout, 0, 0)
readline.clearScreenDown(process.stdout)
}

private _rerunTimer: any
private async scheduleRerun(triggerId: string) {
const currentCount = this.restartsCount
Expand Down Expand Up @@ -459,7 +423,7 @@ export class Vitest {
this.server.close(),
].filter(Boolean)).then((results) => {
results.filter(r => r.status === 'rejected').forEach((err) => {
this.error('error during close', (err as PromiseRejectedResult).reason)
this.logger.error('error during close', (err as PromiseRejectedResult).reason)
})
})
}
Expand Down Expand Up @@ -536,14 +500,6 @@ export class Vitest {
return code.includes('import.meta.vitest')
}

printError(err: unknown, fullStack = false, type?: string) {
return printError(err, this, {
fullStack,
type,
showCodeFrame: true,
})
}

onServerRestarted(fn: () => void) {
this._onRestartListeners.push(fn)
}
Expand Down
26 changes: 15 additions & 11 deletions packages/vitest/src/node/error.ts
Expand Up @@ -10,6 +10,7 @@ import { stringify } from '../integrations/chai/jest-matcher-utils'
import type { Vitest } from './core'
import { type DiffOptions, unifiedDiff } from './diff'
import { divider } from './reporters/renderers/utils'
import type { Logger } from './logger'

export function fileFromParsedStack(stack: ParsedStack) {
if (stack?.sourcePos?.source?.startsWith('..'))
Expand Down Expand Up @@ -47,15 +48,16 @@ export async function printError(error: unknown, ctx: Vitest, options: PrintErro

if (type)
printErrorType(type, ctx)
printErrorMessage(e, ctx.console)

printErrorMessage(e, ctx.logger)
printStack(ctx, stacks, nearest, errorProperties, (s, pos) => {
if (showCodeFrame && s === nearest && nearest) {
const file = fileFromParsedStack(nearest)
// could point to non-existing original file
// for example, when there is a source map file, but no source in node_modules
if (existsSync(file)) {
const sourceCode = readFileSync(file, 'utf-8')
ctx.log(c.yellow(generateCodeFrame(sourceCode, 4, pos)))
ctx.logger.log(c.yellow(generateCodeFrame(sourceCode, 4, pos)))
}
}
})
Expand All @@ -68,15 +70,15 @@ export async function printError(error: unknown, ctx: Vitest, options: PrintErro
handleImportOutsideModuleError(e.stack || e.stackStr || '', ctx)

if (e.showDiff) {
displayDiff(stringify(e.actual), stringify(e.expected), ctx.console, {
displayDiff(stringify(e.actual), stringify(e.expected), ctx.logger.console, {
outputTruncateLength: ctx.config.outputTruncateLength,
outputDiffLines: ctx.config.outputDiffLines,
})
}
}

function printErrorType(type: string, ctx: Vitest) {
ctx.error(`\n${c.red(divider(c.bold(c.inverse(` ${type} `))))}`)
ctx.logger.error(`\n${c.red(divider(c.bold(c.inverse(` ${type} `))))}`)
}

const skipErrorProperties = [
Expand Down Expand Up @@ -124,7 +126,7 @@ function handleImportOutsideModuleError(stack: string, ctx: Vitest) {
else
name = name.split('/')[0]

ctx.error(c.yellow(
ctx.logger.error(c.yellow(
`Module ${path} seems to be an ES Module but shipped in a CommonJS package. `
+ `You might want to create an issue to the package ${c.bold(`"${name}"`)} asking `
+ 'them to ship the file in .mjs extension or add "type": "module" in their package.json.'
Expand All @@ -148,9 +150,9 @@ function displayDiff(actual: string, expected: string, console: Console, options
console.error(c.gray(unifiedDiff(actual, expected, options)) + '\n')
}

function printErrorMessage(error: ErrorWithDiff, console: Console) {
function printErrorMessage(error: ErrorWithDiff, logger: Logger) {
const errorName = error.name || error.nameStr || 'Unknown Error'
console.error(c.red(`${c.bold(errorName)}: ${error.message}`))
logger.error(c.red(`${c.bold(errorName)}: ${error.message}`))
}

function printStack(
Expand All @@ -163,25 +165,27 @@ function printStack(
if (!stack.length)
return

const logger = ctx.logger

for (const frame of stack) {
const pos = frame.sourcePos || frame
const color = frame === highlight ? c.yellow : c.gray
const file = fileFromParsedStack(frame)
const path = relative(ctx.config.root, file)

ctx.log(color(` ${c.dim(F_POINTER)} ${[frame.method, c.dim(`${path}:${pos.line}:${pos.column}`)].filter(Boolean).join(' ')}`))
logger.log(color(` ${c.dim(F_POINTER)} ${[frame.method, c.dim(`${path}:${pos.line}:${pos.column}`)].filter(Boolean).join(' ')}`))
onStack?.(frame, pos)

// reached at test file, skip the follow stack
if (frame.file in ctx.state.filesMap)
break
}
ctx.log()
logger.log()
const hasProperties = Object.keys(errorProperties).length > 0
if (hasProperties) {
ctx.log(c.red(c.dim(divider())))
logger.log(c.red(c.dim(divider())))
const propertiesString = stringify(errorProperties, 10, { printBasicPrototype: false })
ctx.log(c.red(c.bold('Serialized Error:')), c.gray(propertiesString))
logger.log(c.red(c.bold('Serialized Error:')), c.gray(propertiesString))
}
}

Expand Down
118 changes: 118 additions & 0 deletions packages/vitest/src/node/logger.ts
@@ -0,0 +1,118 @@
import { createLogUpdate } from 'log-update'
import c from 'picocolors'
import { version } from '../../../../package.json'
import type { ErrorWithDiff } from '../types'
import { divider } from './reporters/renderers/utils'
import type { Vitest } from './core'
import { printError } from './error'

export class Logger {
outputStream = process.stdout
errorStream = process.stderr
logUpdate = createLogUpdate(process.stdout)

private _clearScreenPending: string | undefined

constructor(
public ctx: Vitest,
public console = globalThis.console,
) {

}

log(...args: any[]) {
this._clearScreen()
this.console.log(...args)
}

error(...args: any[]) {
this._clearScreen()
this.console.error(...args)
}

warn(...args: any[]) {
this._clearScreen()
this.console.warn(...args)
}

clearScreen(message: string, force = false) {
if (this.ctx.server.config.clearScreen === false) {
this.console.log(message)
return
}

this._clearScreenPending = message
if (force)
this._clearScreen()
}

private _clearScreen() {
if (!this._clearScreenPending)
return

const log = this._clearScreenPending
this._clearScreenPending = undefined
// equivalent to ansi-escapes:
// stdout.write(ansiEscapes.cursorTo(0, 0) + ansiEscapes.eraseDown + log)
this.console.log(`\u001B[1;1H\u001B[J${log}`)
}

printError(err: unknown, fullStack = false, type?: string) {
return printError(err, this.ctx, {
fullStack,
type,
showCodeFrame: true,
})
}

printNoTestFound(filters?: string[]) {
const config = this.ctx.config
const comma = c.dim(', ')
if (filters?.length)
this.console.error(c.dim('filter: ') + c.yellow(filters.join(comma)))
if (config.include)
this.console.error(c.dim('include: ') + c.yellow(config.include.join(comma)))
if (config.exclude)
this.console.error(c.dim('exclude: ') + c.yellow(config.exclude.join(comma)))
if (config.watchExclude)
this.console.error(c.dim('watch exclude: ') + c.yellow(config.watchExclude.join(comma)))

if (config.passWithNoTests)
this.log('No test files found, exiting with code 0\n')
else
this.error(c.red('\nNo test files found, exiting with code 1'))
}

printBanner() {
this.log()

const versionTest = this.ctx.config.watch
? c.blue(`v${version}`)
: c.cyan(`v${version}`)
const mode = this.ctx.config.watch
? c.blue(' DEV ')
: c.cyan(' RUN ')

this.log(`${c.inverse(c.bold(mode))} ${versionTest} ${c.gray(this.ctx.config.root)}`)

if (this.ctx.config.ui)
this.log(c.dim(c.green(` UI started at http://${this.ctx.config.api?.host || 'localhost'}:${c.bold(`${this.ctx.server.config.server.port}`)}${this.ctx.config.uiBase}`)))
else if (this.ctx.config.api)
this.log(c.dim(c.green(` API started at http://${this.ctx.config.api?.host || 'localhost'}:${c.bold(`${this.ctx.config.api.port}`)}`)))

this.log()
}

async printUnhandledErrors(errors: unknown[]) {
const errorMessage = c.red(c.bold(
`\nVitest caught ${errors.length} unhandled error${errors.length > 1 ? 's' : ''} during the test run. This might cause false positive tests.`
+ '\nPlease, resolve all the errors to make sure your tests are not affected.',
))
this.log(c.red(divider(c.bold(c.inverse(' Unhandled Errors ')))))
this.log(errorMessage)
await Promise.all(errors.map(async (err) => {
await this.printError(err, true, (err as ErrorWithDiff).type || 'Unhandled Error')
}))
this.log(c.red(divider()))
}
}
6 changes: 3 additions & 3 deletions packages/vitest/src/node/plugins/globalSetup.ts
Expand Up @@ -65,8 +65,8 @@ export const GlobalSetupPlugin = (ctx: Vitest): Plugin => {
}
}
catch (e) {
ctx.error(`\n${c.red(divider(c.bold(c.inverse(' Error during global setup '))))}`)
await ctx.printError(e)
ctx.logger.error(`\n${c.red(divider(c.bold(c.inverse(' Error during global setup '))))}`)
await ctx.logger.printError(e)
process.exit(1)
}
},
Expand All @@ -78,7 +78,7 @@ export const GlobalSetupPlugin = (ctx: Vitest): Plugin => {
await globalSetupFile.teardown?.()
}
catch (error) {
console.error(`error during global teardown of ${globalSetupFile.file}`, error)
ctx.logger.error(`error during global teardown of ${globalSetupFile.file}`, error)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/src/node/plugins/index.ts
Expand Up @@ -133,7 +133,7 @@ export async function VitestPlugin(options: UserConfig = {}, ctx = new Vitest())
(await import('../../api/setup')).setup(ctx)
}
catch (err) {
ctx.printError(err, true)
ctx.logger.printError(err, true)
process.exit(1)
}

Expand Down

0 comments on commit ffa3585

Please sign in to comment.