[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">
This commit is contained in:
Mofei Zhang
2023-08-14 19:26:05 -04:00
parent d2983888f4
commit a418e35bf9
3 changed files with 107 additions and 31 deletions
@@ -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",
+2 -2
View File
@@ -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`.
+104 -28
View File
@@ -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<string>, b: Array<string>) {
if (a.length !== b.length) {
return false;
@@ -43,33 +63,93 @@ function logsEqual(a: Array<string>, b: Array<string>) {
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<void> {
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<string, TestFixture> = 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<void> {
}
}
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));