[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));