125 lines
3.6 KiB
TypeScript
125 lines
3.6 KiB
TypeScript
import ansis from "ansis"
|
|
|
|
import type { CommandResult } from "./run.ts"
|
|
import type { DiffLine } from "./match.ts"
|
|
import { diff, matchOutput } from "./match.ts"
|
|
|
|
export type TestResult = {
|
|
path: string
|
|
passed: boolean
|
|
commandCount: number
|
|
failures: FailedCommand[]
|
|
error?: string
|
|
}
|
|
|
|
type FailedCommand = {
|
|
result: CommandResult
|
|
diffLines: DiffLine[]
|
|
exitCodeMismatch: boolean
|
|
}
|
|
|
|
export function evaluateFile(
|
|
path: string,
|
|
results: CommandResult[],
|
|
error?: string,
|
|
): TestResult {
|
|
if (error) {
|
|
return { path, passed: false, commandCount: results.length, failures: [], error }
|
|
}
|
|
|
|
const failures: FailedCommand[] = []
|
|
|
|
for (const result of results) {
|
|
const { command, actual, exitCode } = result
|
|
const outputMatches = matchOutput(command.expected, actual)
|
|
|
|
let exitCodeMismatch = false
|
|
if (command.exitCode === null) {
|
|
// Expect exit code 0
|
|
exitCodeMismatch = exitCode !== 0
|
|
} else if (command.exitCode === "*") {
|
|
// Expect any non-zero
|
|
exitCodeMismatch = exitCode === 0
|
|
} else {
|
|
// Expect specific code
|
|
exitCodeMismatch = exitCode !== command.exitCode
|
|
}
|
|
|
|
if (!outputMatches || exitCodeMismatch) {
|
|
failures.push({
|
|
result,
|
|
diffLines: outputMatches ? [] : diff(command.expected, actual),
|
|
exitCodeMismatch,
|
|
})
|
|
}
|
|
}
|
|
|
|
return { path, passed: failures.length === 0, commandCount: results.length, failures }
|
|
}
|
|
|
|
export function formatFailure(test: TestResult): string {
|
|
const lines: string[] = []
|
|
|
|
lines.push(ansis.red(`FAIL ${test.path}`))
|
|
|
|
if (test.error) {
|
|
lines.push(` ${ansis.red(test.error)}`)
|
|
return lines.join("\n")
|
|
}
|
|
|
|
for (const failure of test.failures) {
|
|
lines.push("")
|
|
lines.push(` ${ansis.dim("$")} ${failure.result.command.command}`)
|
|
|
|
if (failure.diffLines.length > 0) {
|
|
const expectedLines: string[] = []
|
|
const actualLines: string[] = []
|
|
for (const dl of failure.diffLines) {
|
|
const text = dl.kind === "context" ? ansis.dim(dl.text) : dl.text
|
|
if (dl.kind === "expected" || dl.kind === "equal" || dl.kind === "context") {
|
|
const prefix = dl.kind === "expected" ? ansis.green(" > ") : " "
|
|
expectedLines.push(`${prefix}${text}`)
|
|
}
|
|
if (dl.kind === "actual" || dl.kind === "equal" || dl.kind === "context") {
|
|
const prefix = dl.kind === "actual" ? ansis.red(" > ") : " "
|
|
actualLines.push(`${prefix}${text}`)
|
|
}
|
|
}
|
|
lines.push(ansis.green(" expected:"), ...expectedLines)
|
|
lines.push(ansis.red(" actual:"), ...actualLines)
|
|
}
|
|
|
|
if (failure.exitCodeMismatch) {
|
|
const expected = failure.result.command.exitCode ?? 0
|
|
const actual = failure.result.exitCode
|
|
lines.push(
|
|
ansis.green(` expected exit code: ${expected === "*" ? "non-zero" : expected}`),
|
|
)
|
|
lines.push(ansis.red(` actual exit code: ${actual}`))
|
|
}
|
|
}
|
|
|
|
return lines.join("\n")
|
|
}
|
|
|
|
export function formatSummary(
|
|
results: TestResult[],
|
|
elapsed: number,
|
|
singleFile?: string,
|
|
): string {
|
|
const totalCommands = results.reduce((n, r) => n + r.commandCount, 0)
|
|
const failedCommands = results.reduce((n, r) => n + r.failures.length, 0)
|
|
const passedCommands = totalCommands - failedCommands
|
|
|
|
const parts: string[] = []
|
|
if (passedCommands > 0) parts.push(ansis.green(`${passedCommands} passed`))
|
|
if (failedCommands > 0) parts.push(ansis.red(`${failedCommands} failed`))
|
|
|
|
const time = elapsed < 1000
|
|
? `${Math.round(elapsed)}ms`
|
|
: `${(elapsed / 1000).toFixed(1)}s`
|
|
|
|
const label = singleFile ? ` in ${singleFile}` : ""
|
|
return `${parts.join(", ")}${label} ${ansis.dim(`[${time}]`)}`
|
|
}
|