shout/src/format.ts

120 lines
3.3 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
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, 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, 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) {
lines.push(ansis.red(" expected:"))
for (const dl of failure.diffLines) {
if (dl.kind === "expected" || dl.kind === "equal" || dl.kind === "context") {
const prefix = dl.kind === "expected" ? ansis.red(" > ") : " "
lines.push(`${prefix}${dl.kind === "context" ? ansis.dim(dl.text) : dl.text}`)
}
}
lines.push(ansis.green(" actual:"))
for (const dl of failure.diffLines) {
if (dl.kind === "actual" || dl.kind === "equal" || dl.kind === "context") {
const prefix = dl.kind === "actual" ? ansis.green(" > ") : " "
lines.push(`${prefix}${dl.kind === "context" ? ansis.dim(dl.text) : dl.text}`)
}
}
}
if (failure.exitCodeMismatch) {
const expected = failure.result.command.exitCode ?? 0
const actual = failure.result.exitCode
lines.push(
ansis.red(` expected exit code: ${expected === "*" ? "non-zero" : expected}`),
)
lines.push(ansis.green(` actual exit code: ${actual}`))
}
}
return lines.join("\n")
}
export function formatSummary(
results: TestResult[],
elapsed: number,
): string {
const passed = results.filter(r => r.passed).length
const failed = results.filter(r => !r.passed).length
const parts: string[] = []
if (passed > 0) parts.push(ansis.green(`${passed} passed`))
if (failed > 0) parts.push(ansis.red(`${failed} failed`))
const time = elapsed < 1000
? `${Math.round(elapsed)}ms`
: `${(elapsed / 1000).toFixed(1)}s`
return `${parts.join(", ")} in ${time}`
}