diff --git a/.eslintignore b/.eslintignore index 4095bf672..8e7410c53 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,7 +3,6 @@ /tests/format/**/*.js /tests/integration/cli/ !/tests/format/**/jsfmt.spec.js -/tests/config/jest-light-runner/**/* !/**/.eslintrc.js /test*.* /scripts/release/node_modules diff --git a/.prettierignore b/.prettierignore index f3d63e374..f5cf666ce 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,7 +7,6 @@ coverage/ /tests/integration/cli/ /tests/integration/plugins/ /tests/integration/custom-parsers/ -/tests/config/jest-light-runner/**/* /website/build/ /website/static/lib/ /website/static/playground.js diff --git a/cspell.json b/cspell.json index 1be5b6c0e..784eb95d5 100644 --- a/cspell.json +++ b/cspell.json @@ -215,6 +215,7 @@ "neoclide", "neoformat", "neovim", + "nicolo", "nocheck", "nnoremap", "nonenumerable", @@ -276,6 +277,7 @@ "regexes", "Reimplement", "rhengles", + "ribaudo", "Roadmap", "Rubocop", "ruleset", @@ -391,7 +393,6 @@ ".git", "cspell.json", "*.{log,svg,snap,png}", - "tests/config/jest-light-runner/**/*", "website/data/users.yml", "website/build/**", "website/playground/codeSamples.mjs", diff --git a/jest-format-test.config.mjs b/jest-format-test.config.mjs index 73d208f03..d3bf7a508 100644 --- a/jest-format-test.config.mjs +++ b/jest-format-test.config.mjs @@ -2,7 +2,7 @@ import baseConfig from "./jest.config.mjs"; const config = { ...baseConfig, - runner: "./tests/config/jest-light-runner", + runner: "jest-light-runner", testRegex: "tests/format/.*/jsfmt\\.spec\\.js$", setupFiles: ["/tests/config/format-test-setup.js"], projects: [], diff --git a/package.json b/package.json index d0f5b1219..844043d57 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,6 @@ "@esbuild-plugins/node-modules-polyfill": "0.1.4", "@glimmer/reference": "0.84.1", "@jest/globals": "27.4.2", - "@prettier/jest-light-runner": "file:./tests/config/jest-light-runner", "@types/estree": "0.0.51", "@types/jest": "27.4.1", "@typescript-eslint/eslint-plugin": "5.17.0", @@ -124,6 +123,7 @@ "esm-utils": "3.0.0", "execa": "6.1.0", "jest": "27.5.1", + "jest-light-runner": "nicolo-ribaudo/jest-light-runner#1c31422", "jest-snapshot-serializer-ansi": "1.0.0", "jest-snapshot-serializer-raw": "1.2.0", "jest-watch-typeahead": "1.0.0", diff --git a/tests/config/format-test-setup.js b/tests/config/format-test-setup.js index c185de5bc..83d496fe2 100644 --- a/tests/config/format-test-setup.js +++ b/tests/config/format-test-setup.js @@ -14,6 +14,4 @@ async function setup() { globalThis.run_spec = runSpec; } -// `await`ed in `./jest-light-runner/worker-runner.js` -// Use top-level await in `./prettier-entry.js` when we drop support for Node.js 12 -export { setup }; +export default setup; diff --git a/tests/config/jest-light-runner/package.json b/tests/config/jest-light-runner/package.json deleted file mode 100644 index 2dc542108..000000000 --- a/tests/config/jest-light-runner/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "@prettier/jest-light-runner", - "version": "0.0.1", - "private": true, - "type": "module", - "main": "./src/index.js", - "exports": { - ".": "./src/index.js" - }, - "dependencies": { - "expect": "^27.4.0", - "jest-circus": "^27.4.0", - "jest-each": "^27.4.0", - "jest-mock": "^27.4.0", - "jest-snapshot": "^27.4.0", - "piscina": "^3.1.0", - "supports-color": "^9.0.2" - } -} diff --git a/tests/config/jest-light-runner/readme.md b/tests/config/jest-light-runner/readme.md deleted file mode 100644 index 11ed04e27..000000000 --- a/tests/config/jest-light-runner/readme.md +++ /dev/null @@ -1,6 +0,0 @@ -> This package is copied from https://github.com/babel/babel/tree/main/test/jest-light-runner - -Modifications: - -- Added `setupFiles` support -- Added `snapshotSerializers` support diff --git a/tests/config/jest-light-runner/src/global-setup.js b/tests/config/jest-light-runner/src/global-setup.js deleted file mode 100644 index 6db8e0197..000000000 --- a/tests/config/jest-light-runner/src/global-setup.js +++ /dev/null @@ -1,27 +0,0 @@ -/* eslint-disable import/extensions */ - -import mock from "jest-mock"; -import expect from "expect"; -import snapshot from "jest-snapshot"; -import * as circus from "jest-circus"; - -expect.extend({ - toMatchInlineSnapshot: snapshot.toMatchInlineSnapshot, - toMatchSnapshot: snapshot.toMatchSnapshot, - toThrowErrorMatchingInlineSnapshot: - snapshot.toThrowErrorMatchingInlineSnapshot, - toThrowErrorMatchingSnapshot: snapshot.toThrowErrorMatchingSnapshot, -}); - -globalThis.expect = expect; -globalThis.test = circus.test; -globalThis.it = circus.it; -globalThis.describe = circus.describe; -globalThis.beforeAll = circus.beforeAll; -globalThis.afterAll = circus.afterAll; -globalThis.beforeEach = circus.beforeEach; -globalThis.afterEach = circus.afterEach; -globalThis.jest = { - fn: mock.fn, - spyOn: mock.spyOn, -}; diff --git a/tests/config/jest-light-runner/src/index.js b/tests/config/jest-light-runner/src/index.js deleted file mode 100644 index 8147782e0..000000000 --- a/tests/config/jest-light-runner/src/index.js +++ /dev/null @@ -1,54 +0,0 @@ -import { Piscina } from "piscina"; -import supportsColor from "supports-color"; -import { MessageChannel } from "worker_threads"; -import os from "os"; - -/** @typedef {import("@jest/test-result").Test} Test */ - -const piscina = new Piscina({ - filename: new URL("./worker-runner.js", import.meta.url).href, - maxThreads: os.cpus().length / 2, - env: { - // Workers don't have a tty; we whant them to inherit - // the color support level from the main thread. - FORCE_COLOR: supportsColor.stdout.level, - ...process.env, - }, -}); - -export default class LightRunner { - #config; - - constructor(config) { - this.#config = config; - } - - /** - * @param {Array} tests - * @param {*} watcher - * @param {*} onStart - * @param {*} onResult - * @param {*} onFailure - */ - runTests(tests, watcher, onStart, onResult, onFailure) { - const { updateSnapshot } = this.#config; - - return Promise.all( - tests.map((test) => { - const mc = new MessageChannel(); - mc.port2.onmessage = () => onStart(test); - mc.port2.unref(); - - return piscina - .run( - { test, updateSnapshot, port: mc.port1 }, - { transferList: [mc.port1] } - ) - .then( - (result) => void onResult(test, result), - (error) => void onFailure(test, error) - ); - }) - ); - } -} diff --git a/tests/config/jest-light-runner/src/worker-runner.js b/tests/config/jest-light-runner/src/worker-runner.js deleted file mode 100644 index cdaa05e23..000000000 --- a/tests/config/jest-light-runner/src/worker-runner.js +++ /dev/null @@ -1,239 +0,0 @@ -import path from "path"; -import { fileURLToPath, pathToFileURL } from "url"; -import { performance } from "perf_hooks"; -import snapshot from "jest-snapshot"; -import expect from "expect"; -import * as circus from "jest-circus"; - -import "./global-setup.js"; - -/** @typedef {{ failures: number, passses: number, pending: number, start: number, end: number }} Stats */ -/** @typedef {{ ancestors: string[], title: string, errors: Error[], skipped: boolean }} InternalTestResult */ - -// Node.js workers (worker_therads) don't support -// process.chdir, that we use multiple times in our tests. -// We can "polyfill" it for process.cwd() usage, but it -// won't affect path.* and fs.* functions. -{ - const startCwd = process.cwd(); - let cwd = startCwd; - process.cwd = () => cwd; - process.chdir = dir => { - cwd = path.resolve(cwd, dir); - }; -} - -export default async function ({ test, updateSnapshot, port }) { - port.postMessage("start"); - - const {setupFiles, snapshotSerializers} = test.context.config; - for (const path of setupFiles) { - const { setup } = await import(pathToFileURL(path)); - - if (typeof setup === "function") { - await setup(); - } - } - for (const path of snapshotSerializers) { - const {default: serializer} = await import(pathToFileURL(path)); - snapshot.addSerializer(serializer); - } - - /** @type {Stats} */ - const stats = { passes: 0, failures: 0, pending: 0, start: 0, end: 0 }; - /** @type {Array} */ - const results = []; - - const { tests, hasFocusedTests } = await loadTests(test.path); - - const snapshotState = new snapshot.SnapshotState( - `${path.dirname(test.path)}/__snapshots__/${path.basename(test.path)}.snap`, - { prettierPath: "prettier", updateSnapshot } - ); - expect.setState({ snapshotState }); - - stats.start = performance.now(); - await runTestBlock(tests, hasFocusedTests, results, stats); - stats.end = performance.now(); - - snapshotState._inlineSnapshots.forEach(({ frame }) => { - // When using native ESM, errors have an URL location. - // Jest expects paths. - frame.file = fileURLToPath(frame.file); - }); - snapshotState.save(); - - return toTestResult(stats, results, test); -} - -async function loadTests(testFile) { - circus.resetState(); - await import(pathToFileURL(testFile)); - const { rootDescribeBlock, hasFocusedTests } = circus.getState(); - return { tests: rootDescribeBlock, hasFocusedTests }; -} - -async function runTestBlock( - block, - hasFocusedTests, - results, - stats, - ancestors = [] -) { - await runHooks("beforeAll", block, results, stats, ancestors); - - for (const child of block.children) { - const { type, mode, fn, name } = child; - const nextAncestors = ancestors.concat(name); - - if ( - mode === "skip" || - (hasFocusedTests && type === "test" && mode !== "only") - ) { - stats.pending++; - results.push({ ancestors, title: name, errors: [], skipped: true }); - } else if (type === "describeBlock") { - await runTestBlock(child, hasFocusedTests, results, stats, nextAncestors); - } else if (type === "test") { - await runHooks("beforeEach", block, results, stats, nextAncestors, true); - await runTest(fn, stats, results, ancestors, name); - await runHooks("afterEach", block, results, stats, nextAncestors, true); - } - } - - await runHooks("afterAll", block, results, stats, ancestors); - - return results; -} - -/** - * @param {Function} fn - * @param {Stats} stats - * @param {Array} results - * @param {string[]} ancestors - * @param {string} name - */ -async function runTest(fn, stats, results, ancestors, name) { - expect.setState({ - suppressedErrors: [], - currentTestName: ancestors.concat(name).join(" "), - }); - - const errors = []; - await callAsync(fn).catch(error => { - errors.push(error); - }); - - // Get suppressed errors from ``jest-matchers`` that weren't throw during - // test execution and add them to the test result, potentially failing - // a passing test. - const { suppressedErrors } = expect.getState(); - expect.setState({ suppressedErrors: [] }); - if (suppressedErrors.length > 0) { - errors.unshift(...suppressedErrors); - } - - if (errors.length > 0) { - stats.failures++; - } else { - stats.passes++; - } - results.push({ ancestors, title: name, errors, skipped: false }); -} - -async function runHooks(hook, block, results, stats, ancestors, runInParents) { - for (const { type, fn } of block.hooks) { - if (type === hook) { - await callAsync(fn).catch(error => { - stats.failures++; - results.push({ ancestors, title: `(${hook})`, error, skipped: false }); - }); - } - } - - if (block.parent && runInParents) { - await runHooks(hook, block.parent, results, stats, ancestors, true); - } -} - -function callAsync(fn) { - if (fn.length >= 1) { - return new Promise((resolve, reject) => { - fn((err, result) => { - if (err) { - reject(err); - } else { - resolve(result); - } - }); - }); - } else { - return Promise.resolve().then(fn); - } -} - -/** - * - * @param {Stats} stats - * @param {Array} tests - * @param {import("@jest/test-result").Test} testInput - * @returns {import("@jest/test-result").TestResult} - */ -function toTestResult(stats, tests, { path, context }) { - const { start, end } = stats; - const runtime = end - start; - - return { - coverage: globalThis.__coverage__, - console: null, - failureMessage: tests - .filter(t => t.errors.length > 0) - .map(failureToString) - .join("\n"), - numFailingTests: stats.failures, - numPassingTests: stats.passes, - numPendingTests: stats.pending, - perfStats: { - start, - end, - runtime: Math.round(runtime), // ms precision - slow: runtime / 1000 > context.config.slowTestThreshold, - }, - skipped: false, - snapshot: { - added: 0, - fileDeleted: false, - matched: 0, - unchecked: 0, - unmatched: 0, - updated: 0, - }, - sourceMaps: {}, - testExecError: null, - testFilePath: path, - testResults: tests.map(test => { - return { - ancestorTitles: test.ancestors, - duration: test.duration / 1000, - failureMessages: test.errors.length ? [failureToString(test)] : [], - fullName: test.title, - numPassingAsserts: test.errors.length > 0 ? 1 : 0, - status: test.skipped - ? "pending" - : test.errors.length > 0 - ? "failed" - : "passed", - title: test.title, - }; - }), - }; -} - -function failureToString(test) { - return ( - test.ancestors.concat(test.title).join(" > ") + - "\n" + - test.errors.map(e => e.toString().replace(/^/gm, "\t")).join("\n") + - "\n" - ); -} diff --git a/yarn.lock b/yarn.lock index cf5dd9ba2..7feaaf52b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1571,17 +1571,6 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@prettier/jest-light-runner@file:./tests/config/jest-light-runner": - version "0.0.1" - dependencies: - expect "^27.4.0" - jest-circus "^27.4.0" - jest-each "^27.4.0" - jest-mock "^27.4.0" - jest-snapshot "^27.4.0" - piscina "^3.1.0" - supports-color "^9.0.2" - "@simple-dom/interface@^1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@simple-dom/interface/-/interface-1.4.0.tgz#e8feea579232017f89b0138e2726facda6fbb71f" @@ -3317,7 +3306,7 @@ exit@^0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= -expect@^27.4.0, expect@^27.4.2, expect@^27.5.1: +expect@^27.4.2, expect@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" integrity sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw== @@ -4096,7 +4085,7 @@ jest-changed-files@^27.5.1: execa "^5.0.0" throat "^6.0.1" -jest-circus@^27.4.0, jest-circus@^27.5.1: +jest-circus@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.5.1.tgz#37a5a4459b7bf4406e53d637b49d22c65d125ecc" integrity sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw== @@ -4196,7 +4185,7 @@ jest-docblock@27.5.1, jest-docblock@^27.5.1: dependencies: detect-newline "^3.0.0" -jest-each@^27.4.0, jest-each@^27.5.1: +jest-each@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.5.1.tgz#5bc87016f45ed9507fed6e4702a5b468a5b2c44e" integrity sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ== @@ -4313,6 +4302,18 @@ jest-leak-detector@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" +jest-light-runner@nicolo-ribaudo/jest-light-runner#1c31422: + version "0.1.0" + resolved "https://codeload.github.com/nicolo-ribaudo/jest-light-runner/tar.gz/1c3142229d667fe874749ee3fb91e67ef102ab15" + dependencies: + expect "^27.5.1" + jest-circus "^27.5.1" + jest-each "^27.5.1" + jest-mock "^27.5.1" + jest-snapshot "^27.5.1" + piscina "^3.1.0" + supports-color "^9.2.1" + jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.0, jest-matcher-utils@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" @@ -4353,7 +4354,7 @@ jest-message-util@^27.5.1: slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^27.4.0, jest-mock@^27.5.1: +jest-mock@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" integrity sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og== @@ -4513,7 +4514,7 @@ jest-snapshot@^27.0.4: pretty-format "^27.5.0" semver "^7.3.2" -jest-snapshot@^27.4.0, jest-snapshot@^27.5.1: +jest-snapshot@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.5.1.tgz#b668d50d23d38054a51b42c4039cab59ae6eb6a1" integrity sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA== @@ -6289,10 +6290,10 @@ supports-color@^8.0.0: dependencies: has-flag "^4.0.0" -supports-color@^9.0.2: - version "9.2.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.2.1.tgz#599dc9d45acf74c6176e0d880bab1d7d718fe891" - integrity sha512-Obv7ycoCTG51N7y175StI9BlAXrmgZrFhZOb0/PyjHBher/NmsdBgbbQ1Inhq+gIhz6+7Gb+jWF2Vqi7Mf1xnQ== +supports-color@^9.2.1: + version "9.2.2" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.2.2.tgz#502acaf82f2b7ee78eb7c83dcac0f89694e5a7bb" + integrity sha512-XC6g/Kgux+rJXmwokjm9ECpD6k/smUoS5LKlUCcsYr4IY3rW0XyAympon2RmxGrlnZURMpg5T18gWDP9CsHXFA== supports-hyperlinks@^2.0.0: version "2.2.0"