Skip to content

Commit

Permalink
fix: align json output with jest's (#1034)
Browse files Browse the repository at this point in the history
  • Loading branch information
zxch3n committed Mar 29, 2022
1 parent 6dd7f53 commit 3326332
Show file tree
Hide file tree
Showing 2 changed files with 368 additions and 234 deletions.
131 changes: 101 additions & 30 deletions packages/vitest/src/node/reporters/json.ts
@@ -1,38 +1,62 @@
import { existsSync, promises as fs } from 'fs'
import { dirname, resolve } from 'pathe'
import type { Vitest } from '../../node'
import type { File, Reporter } from '../../types'
import type { File, Reporter, Suite, TaskState } from '../../types'
import { getSuites, getTests } from '../../utils'

// for compatibility reasons, the reporter produces a JSON similar to the one produced by the Jest JSON reporter
// the following types are extracted from the Jest repository (and simplified)
// the commented-out fields are the missing ones

type Status = 'passed' | 'failed' | 'skipped' | 'pending' | 'todo' | 'disabled'
type Milliseconds = number
interface TestResult {
displayName?: string
failureMessage?: string | null
skipped: boolean
status?: string
testFilePath?: string
perfStats: {
end?: Milliseconds
runtime?: Milliseconds
start?: Milliseconds
}
const StatusMap: Record<TaskState, Status> = {
fail: 'failed',
only: 'pending',
pass: 'passed',
run: 'pending',
skip: 'skipped',
todo: 'todo',
}

interface AggregatedResult {
interface FormattedAssertionResult {
ancestorTitles: Array<string>
fullName: string
status: Status
title: string
duration?: Milliseconds | null
failureMessages: Array<string>
// location?: Callsite | null
}

interface FormattedTestResult {
message: string
name: string
status: 'failed' | 'passed'
startTime: number
endTime: number
assertionResults: Array<FormattedAssertionResult>
// summary: string
// coverage: unknown
}

interface FormattedTestResults {
numFailedTests: number
numFailedTestSuites: number
numPassedTests: number
numPassedTestSuites: number
numPendingTests: number
numTodoTests: number
numPendingTestSuites: number
numTodoTests: number
numTotalTests: number
numTotalTestSuites: number
startTime: number
success: boolean
testResults: Array<TestResult>
testResults: Array<FormattedTestResult>
// coverageMap?: CoverageMap | null | undefined
// numRuntimeErrorTestSuites: number
// snapshot: SnapshotSummary
// wasInterrupted: boolean
}

export class JsonReporter implements Reporter {
Expand All @@ -49,31 +73,78 @@ export class JsonReporter implements Reporter {
const numTotalTestSuites = suites.length
const tests = getTests(files)
const numTotalTests = tests.length

const numFailedTestSuites = suites.filter(s => s.result?.error).length
const numPassedTestSuites = numTotalTestSuites - numFailedTestSuites
const numPendingTestSuites = suites.filter(s => s.result?.state === 'run').length
const numFailedTests = tests.filter(t => t.result?.state === 'fail').length
const numPassedTests = numTotalTests - numFailedTests
const numPendingTests = tests.filter(t => t.result?.state === 'run').length
const numTodoTests = tests.filter(t => t.mode === 'todo').length
const testResults: Array<FormattedTestResult> = []

const success = numFailedTestSuites === 0 && numFailedTests === 0

const testResults: Array<TestResult> = tests.map(t => ({
perfStats: {
runtime: t.result?.duration,
start: t.result?.startTime,
end: t.result?.duration && t.result?.startTime && t.result.duration + t.result.startTime,
},
displayName: t.name,
failureMessage: t.result?.error?.message,
skipped: t.mode === 'skip',
status: t.result?.state,
testFilePath: t.file?.filepath,
}))

const result: AggregatedResult = { numTotalTestSuites, numPassedTestSuites, numFailedTestSuites, numPendingTestSuites, numTotalTests, numPassedTests, numFailedTests, numPendingTests, numTodoTests, startTime: this.start, success, testResults }
for (const file of files) {
const tests = getTests([file])
let startTime = tests.reduce((prev, next) => Math.min(prev, next.result?.startTime ?? Infinity), Infinity)
if (startTime === Infinity)
startTime = this.start

const endTime = tests.reduce((prev, next) => Math.max(prev, (next.result?.startTime ?? 0) + (next.result?.duration ?? 0)), startTime)
const assertionResults = tests.map((t) => {
const ancestorTitles = [] as string[]
let iter: Suite | undefined = t.suite
while (iter) {
ancestorTitles.push(iter.name)
iter = iter.suite
}
ancestorTitles.reverse()

return {
ancestorTitles,
fullName: ancestorTitles.length > 0 ? `${ancestorTitles.join(' ')} ${t.name}` : t.name,
status: t.result != null ? StatusMap[t.result.state] : 'skipped',
title: t.name,
duration: t.result?.duration,
failureMessages: t.result?.error?.message == null ? [] : [t.result.error.message],
} as FormattedAssertionResult
})

if (tests.some(t => t.result?.state === 'run')) {
this.ctx.console.warn('WARNING: Some tests are still running when generating the JSON report.'
+ 'This is likely an internal bug in Vitest.'
+ 'Please report it to https://github.com/vitest-dev/vitest/issues')
}

testResults.push({
assertionResults,
startTime,
endTime,
status: tests.every(t =>
t.result?.state === 'pass'
|| t.result?.state === 'skip'
|| t.result?.state === 'todo')
? 'passed'
: 'failed',
message: file.result?.error?.message ?? '',
name: file.filepath,
})
}

const result: FormattedTestResults = {
numTotalTestSuites,
numPassedTestSuites,
numFailedTestSuites,
numPendingTestSuites,
numTotalTests,
numPassedTests,
numFailedTests,
numPendingTests,
numTodoTests,
startTime: this.start,
success,
testResults,
}

await this.writeReport(JSON.stringify(result, null, 2))
}
Expand Down

0 comments on commit 3326332

Please sign in to comment.