Skip to content
This repository has been archived by the owner on Oct 6, 2023. It is now read-only.

Commit

Permalink
feat: completed reporter
Browse files Browse the repository at this point in the history
  • Loading branch information
Akryum committed Dec 20, 2021
1 parent 76df42a commit c058821
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 80 deletions.
3 changes: 0 additions & 3 deletions packages/peeky-cli/src/commands/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,16 @@ import { setupConfigLoader, mergeConfig, PeekyConfig, toProgramConfig } from '@p
import { runAllTests } from '@peeky/runner'
import pick from 'lodash/pick.js'
import consola from 'consola'
import { performance } from 'perf_hooks'

export async function run (quickFilter: string, options) {
try {
const time = performance.now()
const configLoader = await setupConfigLoader()
const config = await configLoader.loadConfig()
await configLoader.destroy()
const finalConfig = mergeConfig(config, (pick<any>(options, [
'match',
'ignore',
]) as PeekyConfig))
consola.info('Setup done in', (performance.now() - time).toFixed(2), 'ms')

const { stats: { errorSuiteCount } } = await runAllTests(toProgramConfig(finalConfig), {
quickTestFilter: quickFilter,
Expand Down
76 changes: 76 additions & 0 deletions packages/peeky-runner/src/reporters/console-fancy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,88 @@ export function createConsoleFancyReporter (): Reporter {
process[type].write(text)
process[type].write('\n')
},

testSuccess: ({ suite, test }) => {
consola.log(chalk.green(`${chalk.bgGreenBright.black.bold(' PASS ')} ${suite.title}${chalk.bold(test.title)} ${chalk.grey(`(${formatDurationToString(test.duration)})`)}`))
},

testFail: ({ suite, test }) => {
consola.log(chalk.red(`${chalk.bgRedBright.black.bold(' FAIL ')} ${suite.title}${chalk.bold(test.title)} ${chalk.grey(`(${formatDurationToString(test.duration)})`)}`))
consola.log(`\n${test.error.stack ?? test.error.message}\n`)
},

errorSummary: ({ suites, errorTestCount, testCount }) => {
consola.log(`\n\n${chalk.red(drawBox(`Summary of ${errorTestCount} / ${testCount} failed tests`, chalk.bold))}\n`)
for (const suite of suites) {
if (suite.testErrors) {
for (const test of suite.tests) {
if (test.error) {
consola.log(chalk.red(`${chalk.bgRedBright.black.bold(' FAIL ')} ${suite.title}${chalk.bold(test.title)}`))
consola.log(`\n${test.error.stack ?? test.error.message}\n`)
}
}
}
}
},

coverageSummary: ({
uncoveredFiles,
partiallyCoveredFiles,
mergedCoverage,
coveredFilesCount,
coveredLines,
totalLines,
}) => {
const header = [
'Coverage',
`${coveredFilesCount}/${mergedCoverage.length} files (${
Math.round(coveredFilesCount / mergedCoverage.length * 10000) / 100
}%)`,
`${coveredLines}/${totalLines} lines (${
Math.round(coveredLines / totalLines * 10000) / 100
}%)`,
]
const noCoverage = coveredFilesCount === 0 && mergedCoverage.length > 0
const headerColor = chalk[noCoverage ? 'red' : coveredLines === totalLines ? 'green' : 'yellow']
const fileCountColor = chalk[noCoverage ? 'red' : coveredFilesCount === mergedCoverage.length ? 'green' : 'yellow']
const headerSeparator = ' '
consola.log(`\n\n${headerColor(drawBox(header.join(headerSeparator), () => chalk.bold([
header[0],
fileCountColor(header[1]),
header[2],
].join(headerSeparator))))}\n`)

const maxFiles = 5

if (uncoveredFiles.length > 0) {
consola.log(chalk.red(`No coverage in ${uncoveredFiles.length} files\n${chalk.dim(`${uncoveredFiles.slice(0, maxFiles).map(c => ` ${c.path}`).join('\n')}${uncoveredFiles.length > maxFiles ? '\n ...' : ''}`)}\n`))
}
if (partiallyCoveredFiles.length > 0) {
consola.log(chalk.yellow(`Partial coverage in ${partiallyCoveredFiles.length} files\n${chalk.dim(`${
partiallyCoveredFiles.slice(0, maxFiles).map(c => ` ${c.path} ${c.linesCovered}/${c.linesTotal} lines (${
Math.round(c.linesCovered / c.linesTotal * 10000) / 100
}%)`).join('\n')
}${partiallyCoveredFiles.length > maxFiles ? '\n ...' : ''}`)}\n`))
}
},

summary: ({
fileCount,
duration,
suiteCount,
errorSuiteCount,
testCount,
errorTestCount,
}) => {
consola.log(`${drawBox(`Ran ${fileCount} tests files (${formatDurationToString(duration)})`)}\n`)
consola.log(chalk[errorSuiteCount ? 'red' : 'green'](` ${chalk.dim('Suites')} ${chalk.bold(`${suiteCount - errorSuiteCount} / ${suiteCount}`)}\n ${chalk.dim('Tests')} ${chalk.bold(`${testCount - errorTestCount} / ${testCount}`)}\n ${chalk.dim('Errors')} ${chalk.bold(errorTestCount)}\n`))
},
}
}

function drawBox (text: string, format?: (text: string) => string) {
let result = `┌${'─'.repeat(text.length + 2)}┐\n`
result += `│ ${format ? format(text) : text} │\n`
result += `└${'─'.repeat(text.length + 2)}┘`
return result
}
71 changes: 24 additions & 47 deletions packages/peeky-runner/src/run-all.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { performance } from 'perf_hooks'
import consola from 'consola'
import chalk from 'chalk'
import { createReactiveFileSystem } from 'reactive-fs'
import { ProgramPeekyConfig } from '@peeky/config'
import { formatDurationToString } from '@peeky/utils'
import { setupRunner } from './runner.js'
import { getStats } from './stats.js'
import { computeCoveredLines, getEmptyCoverageFromFiles, mergeCoverage } from './runtime/coverage.js'
Expand All @@ -14,7 +11,10 @@ export interface RunAllOptions {
}

export async function runAllTests (config: ProgramPeekyConfig, options: RunAllOptions = {}) {
const fsTime = performance.now()
const reporters = [
createConsoleFancyReporter(),
]

const testFiles = await createReactiveFileSystem({
baseDir: config.targetDirectory,
glob: config.match,
Expand All @@ -24,9 +24,7 @@ export async function runAllTests (config: ProgramPeekyConfig, options: RunAllOp
const runner = await setupRunner({
config,
testFiles,
reporters: [
createConsoleFancyReporter(),
],
reporters,
})

let fileList = runner.testFiles.list()
Expand All @@ -36,14 +34,13 @@ export async function runAllTests (config: ProgramPeekyConfig, options: RunAllOp
fileList = fileList.filter(f => reg.test(f))
}

consola.info(`Found ${fileList.length} test files in ${formatDurationToString(performance.now() - fsTime)}.`)

const time = performance.now()
const result = await Promise.all(fileList.map(f => runner.runTestFile(f)))
const endTime = performance.now()

const stats = getStats(result)
const {
suites,
suiteCount,
errorSuiteCount,
testCount,
Expand All @@ -52,25 +49,9 @@ export async function runAllTests (config: ProgramPeekyConfig, options: RunAllOp

// Error summary
if (errorTestCount) {
console.log('―'.repeat(16))
consola.log(chalk.red.bold(`Errors: ${errorTestCount} / ${testCount} tests`))
console.log('―'.repeat(16))
for (const file of result) {
for (const suite of file.suites) {
if (suite.testErrors) {
for (const test of suite.tests) {
if (test.error) {
consola.log(chalk.red(`${chalk.bgRedBright.black.bold(' FAIL ')} ${suite.title}${chalk.bold(test.title)}`))
consola.log(`\n${test.error.stack ?? test.error.message}\n`)
}
}
}
}
}
reporters.forEach(r => r.errorSummary?.({ suites, errorTestCount, testCount }))
}

console.log('―'.repeat(16))

// Coverage
const emptyCoverageIgnored = [
...config.match ?? [],
Expand All @@ -80,33 +61,29 @@ export async function runAllTests (config: ProgramPeekyConfig, options: RunAllOp
let mergedCoverage = mergeCoverage(result.map(r => r.coverage).reduce((a, b) => a.concat(b), []).concat(...emptyCoverage))
mergedCoverage = computeCoveredLines(mergedCoverage)
const uncoveredFiles = mergedCoverage.filter(c => c.linesCovered === 0)
if (uncoveredFiles.length > 0) {
consola.log(chalk.red(`No coverage:\n${uncoveredFiles.map(c => ` ${c.path}`).join('\n')}`))
}
const partiallyCoveredFiles = mergedCoverage.filter(c => c.linesCovered > 0 && c.linesCovered < c.linesTotal)
if (partiallyCoveredFiles.length > 0) {
consola.log(chalk.yellow(`Partial coverage:\n${
partiallyCoveredFiles.map(c => ` ${c.path} | ${c.linesCovered}/${c.linesTotal} lines (${
Math.round(c.linesCovered / c.linesTotal * 10000) / 100
}%)`).join('\n')
}`))
}
const coveredFilesCount = mergedCoverage.length - uncoveredFiles.length
const totalLines = mergedCoverage.reduce((a, c) => a + c.linesTotal, 0)
const coveredLines = mergedCoverage.reduce((a, c) => a + c.linesCovered, 0)
consola.log(chalk[coveredLines === totalLines ? 'green' : 'yellow'].bold(`Coverage: ${
chalk[coveredFilesCount === mergedCoverage.length ? 'green' : 'yellow'](`${coveredFilesCount}/${mergedCoverage.length} files (${
Math.round(coveredFilesCount / mergedCoverage.length * 10000) / 100
}%)`)
} | ${coveredLines}/${totalLines} lines (${
Math.round(coveredLines / totalLines * 10000) / 100
}%)`))
reporters.forEach(r => r.coverageSummary?.({
uncoveredFiles,
partiallyCoveredFiles,
mergedCoverage,
coveredFilesCount,
totalLines,
coveredLines,
}))

// Summary

consola.info(`Ran ${fileList.length} tests files (${formatDurationToString(endTime - time)}, using ${runner.pool.threads.length} parallel workers)`)
consola.log(chalk[errorSuiteCount ? 'red' : 'green'].bold(`Suites : ${suiteCount - errorSuiteCount} / ${suiteCount}\nTests : ${testCount - errorTestCount} / ${testCount}`))
consola.log(chalk[errorSuiteCount ? 'red' : 'green'].bold(`Errors : ${errorTestCount}`))
const duration = endTime - time
reporters.forEach(r => r.summary?.({
fileCount: fileList.length,
duration,
suiteCount,
errorSuiteCount,
testCount,
errorTestCount,
}))

await runner.close()

Expand Down
10 changes: 5 additions & 5 deletions packages/peeky-runner/src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export async function setupRunner (options: RunnerOptions) {

onSuiteStart: (suite) => {
suiteMap[suite.id] = suite
reporters.forEach(r => r?.suiteStart({ suite }))
reporters.forEach(r => r.suiteStart?.({ suite }))
},

onSuiteComplete: ({ id, testErrors, otherErrors }, duration) => {
Expand All @@ -64,7 +64,7 @@ export async function setupRunner (options: RunnerOptions) {
testErrors,
otherErrors,
})
reporters.forEach(r => r?.suiteComplete({ suite }))
reporters.forEach(r => r.suiteComplete?.({ suite }))
},

onTestError: (suiteId, testId, duration, error) => {
Expand All @@ -79,7 +79,7 @@ export async function setupRunner (options: RunnerOptions) {
duration,
error,
})
reporters.forEach(r => r?.testFail({ suite, test }))
reporters.forEach(r => r.testFail?.({ suite, test }))
},

onTestSuccess: (suiteId, testId, duration) => {
Expand All @@ -88,13 +88,13 @@ export async function setupRunner (options: RunnerOptions) {
Object.assign(test, {
duration,
})
reporters.forEach(r => r?.testSuccess({ suite, test }))
reporters.forEach(r => r.testSuccess?.({ suite, test }))
},

onLog: (suiteId, testId, type, text) => {
const suite = suiteMap[suiteId]
const test = suite?.tests.find(t => t.id === testId)
reporters.forEach(r => r?.log({ suite, test, type, text }))
reporters.forEach(r => r.log?.({ suite, test, type, text }))
},
}, handleMessage)

Expand Down
8 changes: 5 additions & 3 deletions packages/peeky-runner/src/runtime/run-test-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { CoverageInstrumenter } from 'collect-v8-coverage'
import fs from 'fs-extra'
import pragma from 'pragma'
import { InstantiableTestEnvironmentClass, mergeConfig } from '@peeky/config'
import type { Context, RunTestFileOptions, TestSuiteResult } from '../types'
import type { Context, ReporterTestSuite, RunTestFileOptions } from '../types'
import { useVite } from './vite.js'
import { getGlobals } from './globals.js'
import { runTests } from './run-tests.js'
Expand Down Expand Up @@ -116,7 +116,7 @@ export async function runTestFile (options: RunTestFileOptions) {
const duration = performance.now() - time

// Result data
const suites: TestSuiteResult[] = ctx.suites.map(s => ({
const suites = ctx.suites.map(s => ({
id: s.id,
title: s.title,
filePath: s.filePath,
Expand All @@ -127,9 +127,11 @@ export async function runTestFile (options: RunTestFileOptions) {
title: t.title,
error: t.error,
flag: t.flag,
duration: t.duration,
})),
runTestCount: s.ranTests.length,
}))
duration: s.duration,
} as ReporterTestSuite))

return {
filePath: options.entry,
Expand Down
11 changes: 7 additions & 4 deletions packages/peeky-runner/src/runtime/run-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,21 @@ export async function runTests (ctx: Context) {
await handler()
}

const time = performance.now()
toMainThread().onTestStart(suite.id, test.id)
const time = performance.now()
try {
await test.handler()
toMainThread().onTestSuccess(suite.id, test.id, performance.now() - time)
test.duration = performance.now() - time
toMainThread().onTestSuccess(suite.id, test.id, test.duration)
} catch (e) {
test.duration = performance.now() - time
test.error = e
let stackIndex = e.stack ? e.stack.lastIndexOf(basename(ctx.options.entry)) : -1
if (stackIndex !== -1) {
// Continue to the end of the line
stackIndex = e.stack.indexOf('\n', stackIndex)
}
toMainThread().onTestError(suite.id, test.id, performance.now() - time, {
toMainThread().onTestError(suite.id, test.id, test.duration, {
message: e.message,
stack: stackIndex !== -1 ? e.stack.substring(0, stackIndex) : e.stack,
data: JSON.stringify(e),
Expand All @@ -79,6 +81,7 @@ export async function runTests (ctx: Context) {
}

suite.ranTests = testsToRun
suite.duration = performance.now() - suiteTime

if (ctx.options.config.emptySuiteError && !testsToRun.length) {
suite.otherErrors.push(new Error(`Empty test suite: ${suite.title}`))
Expand All @@ -88,7 +91,7 @@ export async function runTests (ctx: Context) {
id: suite.id,
testErrors: suite.testErrors,
otherErrors: suite.otherErrors,
}, performance.now() - suiteTime)
}, suite.duration)

setCurrentSuite(null)
}
Expand Down
8 changes: 5 additions & 3 deletions packages/peeky-runner/src/stats.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { RunTestFileResult } from './runner.js'
import { ReporterTestSuite } from './types.js'

export function getStats (results: RunTestFileResult[]) {
let suiteCount = 0
let errorSuiteCount = 0
let testCount = 0
let errorTestCount = 0
const suites: ReporterTestSuite[] = []
for (const file of results) {
suiteCount += file.suites.length
suites.push(...file.suites)
for (const suite of file.suites) {
if (suite.testErrors || suite.otherErrors.length) {
errorSuiteCount++
Expand All @@ -17,9 +18,10 @@ export function getStats (results: RunTestFileResult[]) {
}

return {
suiteCount,
suiteCount: suites.length,
errorSuiteCount,
testCount,
errorTestCount,
suites,
}
}
Loading

0 comments on commit c058821

Please sign in to comment.