@samitouri / QOS-React-2 / commits / a418e35bf9

[sprout] QoL: use cli args, report pretty results

--- - Added sprout to Github Actions by updating `yarn test` command - Added cli args (`filter` and `sync`) - use chalk to make results nicer ✨ Tested locally: <img width="900" alt="Screenshot 2023-08-14 at 6 04 28 PM" src="https://github.com/facebook/react-forget/assets/34200447/b5481bbc-6a50-40c6-a85f-d2443130dd2a">

Mofei Zhang committed Aug 14, 2023 at 19:26 UTC a418e35bf9c99bc65e8c57f0c52e850d0ab1ebab
3 files changed +107 -31
compiler/forget/packages/babel-plugin-react-forget/package.json
+1 -1
@@ -9,7 +9,7 @@
9 ],
10 "scripts": {
11 "build": "rimraf dist && tsc",
12 - "test": "yarn jest && yarn snap:build && yarn snap",
12 + "test": "yarn jest && yarn snap:build && yarn snap && yarn sprout:build && yarn sprout",
13 "jest": "tsc && ts-node \"$(yarn --silent which jest)\"",
14 "snap": "node ../snap/dist/main.js",
15 "sprout": "node ../sprout/dist/main.js",
compiler/forget/packages/sprout/README.md
+2 -2
@@ -6,8 +6,8 @@ Currently, Sprout runs each fixture with a known set of inputs and annotations.
6 Sprout is currently WIP and only executes files listed in `src/SproutOnlyFilterTodoRemove.ts`.
7
8 ### Milestones:
9 -- [✅] Render fixtures with React runtime / `testing-library/react`.
10 -- [ ] Make Sprout CLI -runnable and report results in process exit code.
9 +- [x] Render fixtures with React runtime / `testing-library/react`.
10 +- [x] Make Sprout CLI -runnable and report results in process exit code.
11 After this point:
12 - Sprout can be enabled by default and added to the Github Actions pipeline.
13 - `SproutOnlyFilterTodoRemove` can be renamed to `SproutSkipFilter`.
compiler/forget/packages/sprout/src/runner.ts
+104 -28
@@ -5,13 +5,17 @@
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 +import chalk from "chalk";
9 import { TestFixture } from "fixture-test-utils";
10 import { getFixtures, readTestFilter } from "fixture-test-utils";
11 import { Worker } from "jest-worker";
12 import process from "process";
13 import * as readline from "readline";
14 import * as RunnerWorker from "./runner-worker";
15 +import yargs from "yargs";
16 +import { hideBin } from "yargs/helpers";
17 import SproutOnlyFilterTodoRemove from "./SproutOnlyFilterTodoRemove";
18 +import { FILTER_FILENAME } from "fixture-test-utils";
19
20 const WORKER_PATH = require.resolve("./runner-worker");
21 readline.emitKeypressEvents(process.stdin);
@@ -31,11 +35,27 @@ process.on("SIGTERM", function () {
35 });
36
37 type RunnerOptions = {
34 - useFilter: boolean;
38 + filter: boolean;
39 sync: boolean;
36 - useTodoFilter: boolean;
40 };
41
42 +const opts: RunnerOptions = yargs
43 + .boolean("sync")
44 + .describe(
45 + "sync",
46 + "Run compiler in main thread (instead of using worker threads or subprocesses). Defaults to false."
47 + )
48 + .default("sync", false)
49 + .boolean("filter")
50 + .describe(
51 + "filter",
52 + `Evaluate fixtures in filter mode ("${FILTER_FILENAME}")\n`
53 + )
54 + .default("filter", false)
55 + .help("help")
56 + .strict()
57 + .parseSync(hideBin(process.argv));
58 +
59 function logsEqual(a: Array<string>, b: Array<string>) {
60 if (a.length !== b.length) {
61 return false;
@@ -43,33 +63,93 @@ function logsEqual(a: Array<string>, b: Array<string>) {
63 return a.every((val, idx) => val === b[idx]);
64 }
65
46 -function reportResults(results: Array<[string, RunnerWorker.TestResult]>) {
66 +function reportResults(
67 + results: Array<[string, RunnerWorker.TestResult]>
68 +): boolean {
69 + const failures: Array<[string, RunnerWorker.TestResult]> = [];
70 +
71 for (const [fixtureName, result] of results) {
72 if (result.unexpectedError !== null) {
49 - console.log(`ERROR ${fixtureName}: ${result.unexpectedError}`);
73 + console.log(
74 + chalk.red.inverse.bold(" FAIL ") + " " + chalk.dim(fixtureName)
75 + );
76 + failures.push([fixtureName, result]);
77 continue;
78 }
79 const { forgetResult, nonForgetResult } = result;
53 - if (forgetResult.kind === "UnexpectedError") {
54 - console.log(`ERROR ${fixtureName}: ${forgetResult.value}`);
55 - } else if (nonForgetResult.kind === "UnexpectedError") {
56 - console.log(`ERROR ${fixtureName}: ${nonForgetResult.value}`);
57 - } else if (
80 + if (
81 + forgetResult.kind === "UnexpectedError" ||
82 + nonForgetResult.kind === "UnexpectedError" ||
83 forgetResult.kind !== nonForgetResult.kind ||
84 forgetResult.value !== nonForgetResult.value ||
85 !logsEqual(forgetResult.logs, nonForgetResult.logs)
86 ) {
87 console.log(
63 - `FAIL ${fixtureName}: Difference in forget and non-forget results. \nExpected result: ${JSON.stringify(
64 - forgetResult,
65 - undefined,
66 - 2
67 - )}\nFound: ${JSON.stringify(nonForgetResult, undefined, 2)}`
88 + chalk.red.inverse.bold(" FAIL ") + " " + chalk.dim(fixtureName)
89 );
90 + failures.push([fixtureName, result]);
91 } else {
70 - console.log(`PASS ${fixtureName}`);
92 + console.log(
93 + chalk.green.inverse.bold(" PASS ") + " " + chalk.dim(fixtureName)
94 + );
95 }
96 }
97 +
98 + if (failures.length !== 0) {
99 + console.log("\n" + chalk.red.bold("Failures:") + "\n");
100 +
101 + for (const [fixtureName, result] of failures) {
102 + console.log(chalk.red.bold("FAIL:") + " " + fixtureName);
103 +
104 + if (result.unexpectedError !== null) {
105 + console.log(
106 + chalk.red("Unexpected error when building fixture:") +
107 + ` ${result.unexpectedError}`
108 + );
109 + continue;
110 + }
111 + const { forgetResult, nonForgetResult } = result;
112 + if (forgetResult.kind === "UnexpectedError") {
113 + console.log(
114 + chalk.red(
115 + "Unexpected error when evaluating Forget-transformed fixture:"
116 + ) + ` ${forgetResult.value}`
117 + );
118 + }
119 + if (nonForgetResult.kind === "UnexpectedError") {
120 + console.log(
121 + chalk.red("Unexpected error when evaluating original fixture:") +
122 + ` ${nonForgetResult.value}`
123 + );
124 + }
125 + const hasUnexpectedError =
126 + forgetResult.kind === "UnexpectedError" ||
127 + nonForgetResult.kind === "UnexpectedError";
128 + if (
129 + !hasUnexpectedError &&
130 + (forgetResult.kind !== nonForgetResult.kind ||
131 + forgetResult.value !== nonForgetResult.value ||
132 + !logsEqual(forgetResult.logs, nonForgetResult.logs))
133 + ) {
134 + console.log(
135 + chalk.red("Difference in forget and non-forget results.") +
136 + `\nExpected result: ${JSON.stringify(
137 + forgetResult,
138 + undefined,
139 + 2
140 + )}\nFound: ${JSON.stringify(nonForgetResult, undefined, 2)}`
141 + );
142 + failures.push([fixtureName, result]);
143 + }
144 + }
145 + }
146 +
147 + console.log(
148 + `${results.length} Tests, ${results.length - failures.length} Passed, ${
149 + failures.length
150 + } Failed`
151 + );
152 + return failures.length === 0;
153 }
154
155 /**
@@ -82,16 +162,14 @@ export async function main(opts: RunnerOptions): Promise<void> {
162 worker.getStderr().pipe(process.stderr);
163 worker.getStdout().pipe(process.stdout);
164
85 - const testFilter = opts.useFilter ? await readTestFilter() : null;
165 + const testFilter = opts.filter ? await readTestFilter() : null;
166 let allFixtures: Map<string, TestFixture> = getFixtures(testFilter);
167
88 - if (opts.useTodoFilter) {
89 - allFixtures = new Map(
90 - Array.from(allFixtures.entries()).filter(([filename, _]) =>
91 - SproutOnlyFilterTodoRemove.has(filename)
92 - )
93 - );
94 - }
168 + allFixtures = new Map(
169 + Array.from(allFixtures.entries()).filter(([filename, _]) =>
170 + SproutOnlyFilterTodoRemove.has(filename)
171 + )
172 + );
173
174 const validFixtures = new Map();
175 for (const [name, fixture] of allFixtures) {
@@ -118,10 +196,8 @@ export async function main(opts: RunnerOptions): Promise<void> {
196 }
197 }
198
121 - reportResults(results);
122 - process.exit(0);
199 + const isSuccess = reportResults(results);
200 + process.exit(isSuccess ? 0 : 1);
201 }
202
125 -main({ useFilter: false, sync: true, useTodoFilter: true }).catch((error) =>
126 - console.error(error)
127 -);
203 +main(opts).catch((error) => console.error(error));