120 lines
3.3 KiB
TypeScript
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}`
|
|
}
|