From a418e35bf9c99bc65e8c57f0c52e850d0ab1ebab Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 14 Aug 2023 19:26:05 -0400 Subject: [PATCH] [sprout] QoL: use cli args, report pretty results MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- - Added sprout to Github Actions by updating `yarn test` command - Added cli args (`filter` and `sync`) - use chalk to make results nicer ✨ Tested locally: Screenshot 2023-08-14 at 6 04 28 PM --- .../babel-plugin-react-forget/package.json | 2 +- compiler/forget/packages/sprout/README.md | 4 +- compiler/forget/packages/sprout/src/runner.ts | 132 ++++++++++++++---- 3 files changed, 107 insertions(+), 31 deletions(-) diff --git a/compiler/forget/packages/babel-plugin-react-forget/package.json b/compiler/forget/packages/babel-plugin-react-forget/package.json index 6841e4551d..0495eb3959 100644 --- a/compiler/forget/packages/babel-plugin-react-forget/package.json +++ b/compiler/forget/packages/babel-plugin-react-forget/package.json @@ -9,7 +9,7 @@ ], "scripts": { "build": "rimraf dist && tsc", - "test": "yarn jest && yarn snap:build && yarn snap", + "test": "yarn jest && yarn snap:build && yarn snap && yarn sprout:build && yarn sprout", "jest": "tsc && ts-node \"$(yarn --silent which jest)\"", "snap": "node ../snap/dist/main.js", "sprout": "node ../sprout/dist/main.js", diff --git a/compiler/forget/packages/sprout/README.md b/compiler/forget/packages/sprout/README.md index 83c38d8bf8..ee7dc0878a 100644 --- a/compiler/forget/packages/sprout/README.md +++ b/compiler/forget/packages/sprout/README.md @@ -6,8 +6,8 @@ Currently, Sprout runs each fixture with a known set of inputs and annotations. Sprout is currently WIP and only executes files listed in `src/SproutOnlyFilterTodoRemove.ts`. ### Milestones: -- [✅] Render fixtures with React runtime / `testing-library/react`. -- [ ] Make Sprout CLI -runnable and report results in process exit code. +- [x] Render fixtures with React runtime / `testing-library/react`. +- [x] Make Sprout CLI -runnable and report results in process exit code. After this point: - Sprout can be enabled by default and added to the Github Actions pipeline. - `SproutOnlyFilterTodoRemove` can be renamed to `SproutSkipFilter`. diff --git a/compiler/forget/packages/sprout/src/runner.ts b/compiler/forget/packages/sprout/src/runner.ts index 9c0993d995..e36c3d3530 100644 --- a/compiler/forget/packages/sprout/src/runner.ts +++ b/compiler/forget/packages/sprout/src/runner.ts @@ -5,13 +5,17 @@ * LICENSE file in the root directory of this source tree. */ +import chalk from "chalk"; import { TestFixture } from "fixture-test-utils"; import { getFixtures, readTestFilter } from "fixture-test-utils"; import { Worker } from "jest-worker"; import process from "process"; import * as readline from "readline"; import * as RunnerWorker from "./runner-worker"; +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; import SproutOnlyFilterTodoRemove from "./SproutOnlyFilterTodoRemove"; +import { FILTER_FILENAME } from "fixture-test-utils"; const WORKER_PATH = require.resolve("./runner-worker"); readline.emitKeypressEvents(process.stdin); @@ -31,11 +35,27 @@ process.on("SIGTERM", function () { }); type RunnerOptions = { - useFilter: boolean; + filter: boolean; sync: boolean; - useTodoFilter: boolean; }; +const opts: RunnerOptions = yargs + .boolean("sync") + .describe( + "sync", + "Run compiler in main thread (instead of using worker threads or subprocesses). Defaults to false." + ) + .default("sync", false) + .boolean("filter") + .describe( + "filter", + `Evaluate fixtures in filter mode ("${FILTER_FILENAME}")\n` + ) + .default("filter", false) + .help("help") + .strict() + .parseSync(hideBin(process.argv)); + function logsEqual(a: Array, b: Array) { if (a.length !== b.length) { return false; @@ -43,33 +63,93 @@ function logsEqual(a: Array, b: Array) { return a.every((val, idx) => val === b[idx]); } -function reportResults(results: Array<[string, RunnerWorker.TestResult]>) { +function reportResults( + results: Array<[string, RunnerWorker.TestResult]> +): boolean { + const failures: Array<[string, RunnerWorker.TestResult]> = []; + for (const [fixtureName, result] of results) { if (result.unexpectedError !== null) { - console.log(`ERROR ${fixtureName}: ${result.unexpectedError}`); + console.log( + chalk.red.inverse.bold(" FAIL ") + " " + chalk.dim(fixtureName) + ); + failures.push([fixtureName, result]); continue; } const { forgetResult, nonForgetResult } = result; - if (forgetResult.kind === "UnexpectedError") { - console.log(`ERROR ${fixtureName}: ${forgetResult.value}`); - } else if (nonForgetResult.kind === "UnexpectedError") { - console.log(`ERROR ${fixtureName}: ${nonForgetResult.value}`); - } else if ( + if ( + forgetResult.kind === "UnexpectedError" || + nonForgetResult.kind === "UnexpectedError" || forgetResult.kind !== nonForgetResult.kind || forgetResult.value !== nonForgetResult.value || !logsEqual(forgetResult.logs, nonForgetResult.logs) ) { console.log( - `FAIL ${fixtureName}: Difference in forget and non-forget results. \nExpected result: ${JSON.stringify( - forgetResult, - undefined, - 2 - )}\nFound: ${JSON.stringify(nonForgetResult, undefined, 2)}` + chalk.red.inverse.bold(" FAIL ") + " " + chalk.dim(fixtureName) ); + failures.push([fixtureName, result]); } else { - console.log(`PASS ${fixtureName}`); + console.log( + chalk.green.inverse.bold(" PASS ") + " " + chalk.dim(fixtureName) + ); } } + + if (failures.length !== 0) { + console.log("\n" + chalk.red.bold("Failures:") + "\n"); + + for (const [fixtureName, result] of failures) { + console.log(chalk.red.bold("FAIL:") + " " + fixtureName); + + if (result.unexpectedError !== null) { + console.log( + chalk.red("Unexpected error when building fixture:") + + ` ${result.unexpectedError}` + ); + continue; + } + const { forgetResult, nonForgetResult } = result; + if (forgetResult.kind === "UnexpectedError") { + console.log( + chalk.red( + "Unexpected error when evaluating Forget-transformed fixture:" + ) + ` ${forgetResult.value}` + ); + } + if (nonForgetResult.kind === "UnexpectedError") { + console.log( + chalk.red("Unexpected error when evaluating original fixture:") + + ` ${nonForgetResult.value}` + ); + } + const hasUnexpectedError = + forgetResult.kind === "UnexpectedError" || + nonForgetResult.kind === "UnexpectedError"; + if ( + !hasUnexpectedError && + (forgetResult.kind !== nonForgetResult.kind || + forgetResult.value !== nonForgetResult.value || + !logsEqual(forgetResult.logs, nonForgetResult.logs)) + ) { + console.log( + chalk.red("Difference in forget and non-forget results.") + + `\nExpected result: ${JSON.stringify( + forgetResult, + undefined, + 2 + )}\nFound: ${JSON.stringify(nonForgetResult, undefined, 2)}` + ); + failures.push([fixtureName, result]); + } + } + } + + console.log( + `${results.length} Tests, ${results.length - failures.length} Passed, ${ + failures.length + } Failed` + ); + return failures.length === 0; } /** @@ -82,16 +162,14 @@ export async function main(opts: RunnerOptions): Promise { worker.getStderr().pipe(process.stderr); worker.getStdout().pipe(process.stdout); - const testFilter = opts.useFilter ? await readTestFilter() : null; + const testFilter = opts.filter ? await readTestFilter() : null; let allFixtures: Map = getFixtures(testFilter); - if (opts.useTodoFilter) { - allFixtures = new Map( - Array.from(allFixtures.entries()).filter(([filename, _]) => - SproutOnlyFilterTodoRemove.has(filename) - ) - ); - } + allFixtures = new Map( + Array.from(allFixtures.entries()).filter(([filename, _]) => + SproutOnlyFilterTodoRemove.has(filename) + ) + ); const validFixtures = new Map(); for (const [name, fixture] of allFixtures) { @@ -118,10 +196,8 @@ export async function main(opts: RunnerOptions): Promise { } } - reportResults(results); - process.exit(0); + const isSuccess = reportResults(results); + process.exit(isSuccess ? 0 : 1); } -main({ useFilter: false, sync: true, useTodoFilter: true }).catch((error) => - console.error(error) -); +main(opts).catch((error) => console.error(error));