import chalk from "chalk";
import fs from "fs";
import invariant from "invariant";
import { diff } from "jest-diff";
import path from "path";
function wrapWithTripleBackticks(s: string, ext: string | null = null): string {
return `\`\`\`${ext ?? ""}
${s}
\`\`\``;
}
const SPROUT_SEPARATOR = "\n### Eval output\n";
export function writeOutputToString(
input: string,
compilerOutput: string | null,
evaluatorOutput: string | null,
logs: string | null,
errorMessage: string | null
) {
let result = `
## Input
${wrapWithTripleBackticks(input, "javascript")}
`;
if (compilerOutput != null) {
result += `
## Code
${wrapWithTripleBackticks(compilerOutput, "javascript")}
`;
} else {
result += "\n";
}
if (logs != null) {
result += `
## Logs
${wrapWithTripleBackticks(logs, null)}
`;
}
if (errorMessage != null) {
result += `
## Error
${wrapWithTripleBackticks(errorMessage.replace(/^\/.*?:\s/, ""))}
\n`;
}
result += ` `;
if (evaluatorOutput != null) {
result += SPROUT_SEPARATOR + evaluatorOutput;
}
return result;
}
export type TestResult = {
actual: string | null;
expected: string | null;
outputPath: string;
unexpectedError: string | null;
};
export type TestResults = Map<string, TestResult>;
export async function update(results: TestResults): Promise<void> {
let deleted = 0;
let updated = 0;
let created = 0;
const failed = [];
for (const [basename, result] of results) {
if (result.unexpectedError != null) {
console.log(
chalk.red.inverse.bold(" FAILED ") + " " + chalk.dim(basename)
);
failed.push([basename, result.unexpectedError]);
} else if (result.actual == null) {
console.log(
chalk.red.inverse.bold(" REMOVE ") + " " + chalk.dim(basename)
);
try {
fs.unlinkSync(result.outputPath);
console.log(" remove " + result.outputPath);
deleted++;
} catch (e) {
console.error(
"[Snap tester error]: failed to remove " + result.outputPath
);
failed.push([basename, result.unexpectedError]);
}
} else if (result.actual !== result.expected) {
console.log(
chalk.blue.inverse.bold(" UPDATE ") + " " + chalk.dim(basename)
);
try {
fs.writeFileSync(result.outputPath, result.actual, "utf8");
} catch (e) {
if (e?.code === "ENOENT") {
fs.mkdirSync(path.dirname(result.outputPath), { recursive: true });
fs.writeFileSync(result.outputPath, result.actual, "utf8");
}
}
if (result.expected == null) {
created++;
} else {
updated++;
}
} else {
console.log(
chalk.green.inverse.bold(" OKAY ") + " " + chalk.dim(basename)
);
}
}
console.log(
`${deleted} Deleted, ${created} Created, ${updated} Updated, ${failed.length} Failed`
);
for (const [basename, errorMsg] of failed) {
console.log(`${chalk.red.bold("Fail:")} ${basename}\n${errorMsg}`);
}
}
export function report(results: TestResults): boolean {
const failures: Array<[string, TestResult]> = [];
for (const [basename, result] of results) {
if (result.actual === result.expected && result.unexpectedError == null) {
console.log(
chalk.green.inverse.bold(" PASS ") + " " + chalk.dim(basename)
);
} else {
console.log(chalk.red.inverse.bold(" FAIL ") + " " + chalk.dim(basename));
failures.push([basename, result]);
}
}
if (failures.length !== 0) {
console.log("\n" + chalk.red.bold("Failures:") + "\n");
for (const [basename, result] of failures) {
console.log(chalk.red.bold("FAIL:") + " " + basename);
if (result.unexpectedError != null) {
console.log(
` >> Unexpected error during test: \n${result.unexpectedError}`
);
} else {
if (result.expected == null) {
invariant(result.actual != null, "[Tester] Internal failure.");
console.log(
chalk.red("[ expected fixture output is absent ]") + "\n"
);
} else if (result.actual == null) {
invariant(result.expected != null, "[Tester] Internal failure.");
console.log(
chalk.red(`[ fixture input for ${result.outputPath} is absent ]`) +
"\n"
);
} else {
console.log(diff(result.expected, result.actual) + "\n");
}
}
}
}
console.log(
`${results.size} Tests, ${results.size - failures.length} Passed, ${
failures.length
} Failed`
);
return failures.length === 0;
}