From 9f7d77d438744339543a613642e2d96f80a6917b Mon Sep 17 00:00:00 2001 From: Ricardo Gobbo de Souza Date: Thu, 26 Mar 2026 09:14:12 -0300 Subject: [PATCH 1/2] refactor!: remove threads option --- README.md | 12 ---- package.json | 2 - src/getESLint.js | 134 ++++++++--------------------------- src/index.js | 12 +--- src/linter.js | 16 +---- src/options.js | 1 - src/options.json | 4 -- src/utils.js | 26 ------- src/worker.js | 51 ------------- test/autofix.test.js | 2 +- test/child-compiler.test.js | 2 +- test/error.test.js | 2 +- test/eslint-lint.test.js | 6 +- test/eslintrc-config.test.js | 1 - test/flat-config.test.js | 1 - test/threads.test.js | 74 ------------------- test/utils/conf.js | 2 - types/getESLint.d.ts | 38 +++------- types/linter.d.ts | 5 +- types/options.d.ts | 5 -- types/utils.d.ts | 11 --- types/worker.d.ts | 28 -------- 22 files changed, 49 insertions(+), 386 deletions(-) delete mode 100644 src/worker.js delete mode 100644 test/threads.test.js delete mode 100644 types/worker.d.ts diff --git a/README.md b/README.md index 2afa8a0..8ef78ea 100644 --- a/README.md +++ b/README.md @@ -239,18 +239,6 @@ type lintDirtyModulesOnly = boolean; Lint only changed files, skipping initial lint on build start. -### `threads` - -- Type: - -```ts -type threads = boolean | number; -``` - -- Default: `false` - -Will run lint tasks across a thread pool. The pool size is automatic unless you specify a number. - ### Errors and Warning **By default the plugin will auto adjust error reporting depending on eslint errors/warnings counts.** diff --git a/package.json b/package.json index 7773d42..cdf9786 100644 --- a/package.json +++ b/package.json @@ -51,8 +51,6 @@ }, "dependencies": { "@types/eslint": "^9.6.1", - "flatted": "^3.3.3", - "jest-worker": "^30.2.0", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "schema-utils": "^4.3.3" diff --git a/src/getESLint.js b/src/getESLint.js index 85bf9cb..8969567 100644 --- a/src/getESLint.js +++ b/src/getESLint.js @@ -1,127 +1,51 @@ -const { cpus } = require("node:os"); - -const { stringify } = require("flatted"); -const { Worker: JestWorker } = require("jest-worker"); - const { getESLintOptions } = require("./options"); -const { jsonStringifyReplacerSortKeys } = require("./utils"); -const { lintFiles, setup } = require("./worker"); - -/** @type {{ [key: string]: Linter }} */ -const cache = {}; /** @typedef {import("eslint").ESLint} ESLint */ /** @typedef {import("eslint").ESLint.LintResult} LintResult */ /** @typedef {import("./options").Options} Options */ -/** @typedef {() => Promise} AsyncTask */ /** @typedef {(files: string | string[]) => Promise} LintTask */ -/** @typedef {{ threads: number, eslint: ESLint, lintFiles: LintTask, cleanup: AsyncTask }} Linter */ -/** @typedef {JestWorker & { lintFiles: LintTask }} Worker */ +/** @typedef {{ eslint: ESLint, lintFiles: LintTask }} Linter */ +/** @typedef {import("eslint").ESLint.Options} ESLintOptions */ +/** @typedef {{ new (arg0: ESLintOptions): ESLint, outputFixes: (arg0: LintResult[]) => Promise }} ESLintClass */ /** * @param {Options} options options * @returns {Promise} linter */ -async function loadESLint(options) { - const { eslintPath } = options; - const eslint = await setup({ - eslintPath, - configType: options.configType, - eslintOptions: getESLintOptions(options), +async function getESLint(options) { + const eslintOptions = getESLintOptions(options); + const fix = Boolean(eslintOptions && eslintOptions.fix); + + const eslintModule = require(options.eslintPath || "eslint"); + + /** @type {ESLintClass} */ + const ESLint = await eslintModule.loadESLint({ + useFlatConfig: options.configType === "flat", }); + /** @type {ESLint} */ + const eslint = new ESLint(eslintOptions); + + /** + * @param {string | string[]} files files + * @returns {Promise} lint results + */ + async function lintFiles(files) { + /** @type {LintResult[]} */ + const result = await eslint.lintFiles(files); + // if enabled, use eslint autofixing where possible + if (fix) { + await ESLint.outputFixes(result); + } + return result; + } + return { - threads: 1, lintFiles, eslint, - // no-op for non-threaded - cleanup: async () => {}, - }; -} - -/** - * @param {string | undefined} key a cache key - * @param {Options} options options - * @returns {string} a stringified cache key - */ -function getCacheKey(key, options) { - return stringify({ key, options }, jsonStringifyReplacerSortKeys); -} - -/** - * @param {string | undefined} key a cache key - * @param {number} poolSize number of workers - * @param {Options} options options - * @returns {Promise} linter - */ -async function loadESLintThreaded(key, poolSize, options) { - const cacheKey = getCacheKey(key, options); - const { eslintPath = "eslint" } = options; - const source = require.resolve("./worker"); - const workerOptions = { - enableWorkerThreads: true, - numWorkers: poolSize, - setupArgs: [ - { - eslintPath, - configType: options.configType, - eslintOptions: getESLintOptions(options), - }, - ], - }; - - const local = await loadESLint(options); - - let worker = - /** @type {Worker | null} */ - (new JestWorker(source, workerOptions)); - - /** @type {Linter} */ - const context = { - ...local, - threads: poolSize, - lintFiles: async (files) => - (worker && (await worker.lintFiles(files))) || - /* istanbul ignore next */ [], - cleanup: async () => { - cache[cacheKey] = local; - context.lintFiles = (files) => local.lintFiles(files); - if (worker) { - worker.end(); - worker = null; - } - }, }; - - return context; -} - -/** - * @param {string | undefined} key a cache key - * @param {Options} options options - * @returns {Promise} linter - */ -async function getESLint(key, { threads, ...options }) { - const max = - typeof threads !== "number" - ? threads - ? cpus().length - 1 - : 1 - : /* istanbul ignore next */ - threads; - - const cacheKey = getCacheKey(key, { threads, ...options }); - if (!cache[cacheKey]) { - cache[cacheKey] = - max > 1 - ? await loadESLintThreaded(key, max, options) - : await loadESLint(options); - } - return cache[cacheKey]; } module.exports = { getESLint, - loadESLint, - loadESLintThreaded, }; diff --git a/src/index.js b/src/index.js index 016fd25..0df573d 100644 --- a/src/index.js +++ b/src/index.js @@ -99,15 +99,9 @@ class ESLintWebpackPlugin { let lint; /** @type {import("./linter").Reporter} */ let report; - /** @type number */ - let threads; try { - ({ lint, report, threads } = await linter( - this.key, - options, - compilation, - )); + ({ lint, report } = await linter(options, compilation)); } catch (err) { compilation.errors.push(err); return; @@ -135,8 +129,6 @@ class ESLintWebpackPlugin { if (isFileNotListed && isFileWanted && isQueryNotExclude) { files.push(file); - - if (threads > 1) lint(file); } } @@ -149,7 +141,7 @@ class ESLintWebpackPlugin { // Lint all files added compilation.hooks.finishModules.tap(this.key, () => { - if (files.length > 0 && threads <= 1) lint(files); + if (files.length > 0) lint(files); }); // await and interpret results diff --git a/src/linter.js b/src/linter.js index 3a8f4ad..359938b 100644 --- a/src/linter.js +++ b/src/linter.js @@ -159,29 +159,22 @@ function parseResults(options, results) { } /** - * @param {string | undefined} key a cache key * @param {Options} options options * @param {Compilation} compilation compilation - * @returns {Promise<{ lint: Linter, report: Reporter, threads: number }>} linter with additional functions + * @returns {Promise<{ lint: Linter, report: Reporter }>} linter with additional functions */ -async function linter(key, options, compilation) { +async function linter(options, compilation) { /** @type {ESLint} */ let eslint; /** @type {(files: string | string[]) => Promise} */ let lintFiles; - /** @type {() => Promise} */ - let cleanup; - - /** @type number */ - let threads; - /** @type {Promise[]} */ const rawResults = []; try { - ({ eslint, lintFiles, cleanup, threads } = await getESLint(key, options)); + ({ eslint, lintFiles } = await getESLint(options)); } catch (err) { throw new ESLintError(err.message); } @@ -209,8 +202,6 @@ async function linter(key, options, compilation) { await flatten(rawResults.splice(0)), ); - await cleanup(); - // do not analyze if there are no results or eslint config if (!results || results.length < 1) { return {}; @@ -282,7 +273,6 @@ async function linter(key, options, compilation) { return { lint, report, - threads, }; } diff --git a/src/options.js b/src/options.js index 78da6a2..2dab3f6 100644 --- a/src/options.js +++ b/src/options.js @@ -33,7 +33,6 @@ const schema = require("./options.json"); * @property {boolean=} quiet will process and report errors only and ignore warnings * @property {string=} eslintPath path to `eslint` instance that will be used for linting * @property {OutputReport=} outputReport writes the output of the errors to a file - for example, a `json` file for use for reporting - * @property {number | boolean=} threads number of worker threads * @property {RegExp | RegExp[]=} resourceQueryExclude Specify the resource query to exclude * @property {string=} configType config type */ diff --git a/src/options.json b/src/options.json index 298427c..d1051a6 100644 --- a/src/options.json +++ b/src/options.json @@ -83,10 +83,6 @@ } } ] - }, - "threads": { - "description": "Default is false. Set to true for an auto-selected pool size based on number of cpus. Set to a number greater than 1 to set an explicit pool size. Set to false, 1, or less to disable and only run in main process.", - "anyOf": [{ "type": "number" }, { "type": "boolean" }] } } } diff --git a/src/utils.js b/src/utils.js index e2241e4..dc2dc83 100644 --- a/src/utils.js +++ b/src/utils.js @@ -90,34 +90,8 @@ function parseFoldersToGlobs(patterns, extensions = []) { }); } -/** - * @param {string} _ key, but unused - * @param {EXPECTED_ANY} value value - * @returns {{ [x: string]: EXPECTED_ANY }} result - */ -const jsonStringifyReplacerSortKeys = (_, value) => { - /** - * @param {{ [x: string]: EXPECTED_ANY }} sorted sorted - * @param {string | number} key key - * @returns {{ [x: string]: EXPECTED_ANY }} result - */ - const insert = (sorted, key) => { - sorted[key] = value[key]; - return sorted; - }; - - if (value instanceof Object && !Array.isArray(value)) { - const sorted = Object.keys(value).toSorted().reduce(insert, {}); - for (const key of Object.keys(value)) delete value[key]; - Object.assign(value, sorted); - } - - return value; -}; - module.exports = { arrify, - jsonStringifyReplacerSortKeys, parseFiles, parseFoldersToGlobs, }; diff --git a/src/worker.js b/src/worker.js deleted file mode 100644 index e58daac..0000000 --- a/src/worker.js +++ /dev/null @@ -1,51 +0,0 @@ -/** @typedef {import("eslint").ESLint} ESLint */ -/** @typedef {import("eslint").ESLint.Options} ESLintOptions */ -/** @typedef {import("eslint").ESLint.LintResult} LintResult */ -/** @typedef {{ new (arg0: ESLintOptions): ESLint, outputFixes: (arg0: LintResult[]) => Promise }} ESLintClass */ - -/** @type {ESLintClass} */ -let ESLint; - -/** @type {ESLint} */ -let eslint; - -/** @type {boolean} */ -let fix; - -/** - * @param {object} options setup worker - * @param {string=} options.eslintPath import path of eslint - * @param {string=} options.configType a config type - * @param {ESLintOptions} options.eslintOptions linter options - * @returns {ESLint} eslint - */ -function setup({ eslintPath, configType, eslintOptions }) { - fix = Boolean(eslintOptions && eslintOptions.fix); - - const eslintModule = require(eslintPath || "eslint"); - - return eslintModule - .loadESLint({ useFlatConfig: configType === "flat" }) - .then((/** @type {ESLintClass} */ classESLint) => { - ESLint = classESLint; - eslint = new ESLint(eslintOptions); - return eslint; - }); -} - -/** - * @param {string | string[]} files files - * @returns {Promise} lint results - */ -async function lintFiles(files) { - /** @type {LintResult[]} */ - const result = await eslint.lintFiles(files); - // if enabled, use eslint autofixing where possible - if (fix) { - await ESLint.outputFixes(result); - } - return result; -} - -module.exports.lintFiles = lintFiles; -module.exports.setup = setup; diff --git a/test/autofix.test.js b/test/autofix.test.js index d837ab8..e00fd7a 100644 --- a/test/autofix.test.js +++ b/test/autofix.test.js @@ -15,7 +15,7 @@ describe("autofix stop", () => { removeSync(entry); }); - it.each([[{}], [{ threads: false }]])( + it.each([[{}]])( "should not throw error if file ok after auto-fixing", async (cfg) => { const compiler = pack("fixable-clone", { diff --git a/test/child-compiler.test.js b/test/child-compiler.test.js index 5a37aa9..0cb101d 100644 --- a/test/child-compiler.test.js +++ b/test/child-compiler.test.js @@ -25,7 +25,7 @@ class ChildPlugin { describe("child compiler", () => { it("should have linting process", (done) => { - const config = conf("good", { threads: false }); + const config = conf("good"); config.plugins.push( new ChildPlugin({ entry: { diff --git a/test/error.test.js b/test/error.test.js index 2021d87..1653175 100644 --- a/test/error.test.js +++ b/test/error.test.js @@ -22,7 +22,7 @@ describe("error", () => { }, })); - const compiler = pack("good", { threads: false }); + const compiler = pack("good"); const stats = await compiler.runAsync(); expect(stats.hasWarnings()).toBe(false); diff --git a/test/eslint-lint.test.js b/test/eslint-lint.test.js index 7154f7b..941daca 100644 --- a/test/eslint-lint.test.js +++ b/test/eslint-lint.test.js @@ -25,14 +25,14 @@ describe("eslint lint", () => { }); it("should lint one file", async () => { - const compiler = pack("lint-one", { threads: false }); + const compiler = pack("lint-one"); await compiler.runAsync(); expect(mockLintFiles).toHaveBeenCalledTimes(1); }); it("should lint two files", async () => { - const compiler = pack("lint-two", { threads: false }); + const compiler = pack("lint-two"); await compiler.runAsync(); const files = [ @@ -43,7 +43,7 @@ describe("eslint lint", () => { }); it("should lint more files", async () => { - const compiler = pack("lint-more", { threads: false }); + const compiler = pack("lint-more"); await compiler.runAsync(); const files = [ diff --git a/test/eslintrc-config.test.js b/test/eslintrc-config.test.js index d4561f7..f40137e 100644 --- a/test/eslintrc-config.test.js +++ b/test/eslintrc-config.test.js @@ -16,7 +16,6 @@ import pack from "./utils/pack"; const compiler = pack("full-of-problems", { configType: "eslintrc", overrideConfigFile, - threads: 1, }); const stats = await compiler.runAsync(); diff --git a/test/flat-config.test.js b/test/flat-config.test.js index ad6084f..e7a5be4 100644 --- a/test/flat-config.test.js +++ b/test/flat-config.test.js @@ -7,7 +7,6 @@ describe("succeed on flat-configuration", () => { const compiler = pack("full-of-problems", { configType: "flat", overrideConfigFile, - threads: 1, }); const stats = await compiler.runAsync(); diff --git a/test/threads.test.js b/test/threads.test.js deleted file mode 100644 index 2b68538..0000000 --- a/test/threads.test.js +++ /dev/null @@ -1,74 +0,0 @@ -import { join } from "node:path"; -import { loadESLint, loadESLintThreaded } from "../src/getESLint"; - -describe("Threading", () => { - it("threaded interface should look like non-threaded interface", async () => { - const single = await loadESLint({}); - const threaded = await loadESLintThreaded("foo", 1, {}); - for (const key of Object.keys(single)) { - expect(typeof single[key]).toEqual(typeof threaded[key]); - } - - expect(single.Eslint).toBe(threaded.Eslint); - expect(single.eslint).not.toBe(threaded.eslint); - expect(single.lintFiles).not.toBe(threaded.lintFiles); - expect(single.cleanup).not.toBe(threaded.cleanup); - - single.cleanup(); - threaded.cleanup(); - }); - - it("threaded should lint files", async () => { - const threaded = await loadESLintThreaded("bar", 1, { - configType: "flat", - ignore: false, - overrideConfigFile: join( - __dirname, - "./config-for-tests/eslint.config.mjs", - ), - }); - try { - const [good, bad] = await Promise.all([ - threaded.lintFiles(join(__dirname, "fixtures/good.js")), - threaded.lintFiles(join(__dirname, "fixtures/error.js")), - ]); - expect(good[0].errorCount).toBe(0); - expect(good[0].warningCount).toBe(0); - expect(bad[0].errorCount).toBe(3); - expect(bad[0].warningCount).toBe(0); - } finally { - threaded.cleanup(); - } - }); - - describe("worker coverage", () => { - beforeEach(() => { - jest.resetModules(); - }); - - it("worker can start", async () => { - const mockThread = { parentPort: { on: jest.fn() }, workerData: {} }; - const mockLintFiles = jest.fn(); - jest.mock("eslint", () => ({ - loadESLint: async () => - Object.assign( - function ESLint() { - this.lintFiles = mockLintFiles; - }, - { - outputFixes: jest.fn(), - }, - ), - })); - jest.mock("worker_threads", () => mockThread); - - const { lintFiles, setup } = require("../src/worker"); - - await setup({}); - await lintFiles("foo"); - expect(mockLintFiles).toHaveBeenCalledWith("foo"); - await setup({ eslintOptions: { fix: true } }); - await lintFiles("foo"); - }); - }); -}); diff --git a/test/utils/conf.js b/test/utils/conf.js index dda2752..55567b6 100644 --- a/test/utils/conf.js +++ b/test/utils/conf.js @@ -22,8 +22,6 @@ export default (entry, pluginConf = {}, webpackConf = {}) => { // this disables the use of .eslintignore, since it contains the fixtures // folder to skip it on the global linting, but here we want the opposite ignore: false, - // TODO: update tests to run both states: test.each([[{threads: false}], [{threads: true}]])('it should...', async ({threads}) => {...}) - threads: true, ...pluginConf, }), ], diff --git a/types/getESLint.d.ts b/types/getESLint.d.ts index bd30bde..be7a73e 100644 --- a/types/getESLint.d.ts +++ b/types/getESLint.d.ts @@ -1,47 +1,25 @@ export type ESLint = import("eslint").ESLint; export type LintResult = import("eslint").ESLint.LintResult; export type Options = import("./options").Options; -export type AsyncTask = () => Promise; export type LintTask = (files: string | string[]) => Promise; export type Linter = { - threads: number; eslint: ESLint; lintFiles: LintTask; - cleanup: AsyncTask; }; -export type Worker = JestWorker & { - lintFiles: LintTask; +export type ESLintOptions = import("eslint").ESLint.Options; +export type ESLintClass = { + new (arg0: ESLintOptions): ESLint; + outputFixes: (arg0: LintResult[]) => Promise; }; -/** - * @param {string | undefined} key a cache key - * @param {Options} options options - * @returns {Promise} linter - */ -export function getESLint( - key: string | undefined, - { threads, ...options }: Options, -): Promise; /** @typedef {import("eslint").ESLint} ESLint */ /** @typedef {import("eslint").ESLint.LintResult} LintResult */ /** @typedef {import("./options").Options} Options */ -/** @typedef {() => Promise} AsyncTask */ /** @typedef {(files: string | string[]) => Promise} LintTask */ -/** @typedef {{ threads: number, eslint: ESLint, lintFiles: LintTask, cleanup: AsyncTask }} Linter */ -/** @typedef {JestWorker & { lintFiles: LintTask }} Worker */ -/** - * @param {Options} options options - * @returns {Promise} linter - */ -export function loadESLint(options: Options): Promise; +/** @typedef {{ eslint: ESLint, lintFiles: LintTask }} Linter */ +/** @typedef {import("eslint").ESLint.Options} ESLintOptions */ +/** @typedef {{ new (arg0: ESLintOptions): ESLint, outputFixes: (arg0: LintResult[]) => Promise }} ESLintClass */ /** - * @param {string | undefined} key a cache key - * @param {number} poolSize number of workers * @param {Options} options options * @returns {Promise} linter */ -export function loadESLintThreaded( - key: string | undefined, - poolSize: number, - options: Options, -): Promise; -import { Worker as JestWorker } from "jest-worker"; +export function getESLint(options: Options): Promise; diff --git a/types/linter.d.ts b/types/linter.d.ts index 0b3ed58..6c51995 100644 --- a/types/linter.d.ts +++ b/types/linter.d.ts @@ -1,18 +1,15 @@ export = linter; /** - * @param {string | undefined} key a cache key * @param {Options} options options * @param {Compilation} compilation compilation - * @returns {Promise<{ lint: Linter, report: Reporter, threads: number }>} linter with additional functions + * @returns {Promise<{ lint: Linter, report: Reporter }>} linter with additional functions */ declare function linter( - key: string | undefined, options: Options, compilation: Compilation, ): Promise<{ lint: Linter; report: Reporter; - threads: number; }>; declare namespace linter { export { diff --git a/types/options.d.ts b/types/options.d.ts index 4de096d..90e1680 100644 --- a/types/options.d.ts +++ b/types/options.d.ts @@ -68,10 +68,6 @@ export type PluginOptions = { * writes the output of the errors to a file - for example, a `json` file for use for reporting */ outputReport?: OutputReport | undefined; - /** - * number of worker threads - */ - threads?: (number | boolean) | undefined; /** * Specify the resource query to exclude */ @@ -115,7 +111,6 @@ export function getESLintOptions(loaderOptions: Options): ESLintOptions; * @property {boolean=} quiet will process and report errors only and ignore warnings * @property {string=} eslintPath path to `eslint` instance that will be used for linting * @property {OutputReport=} outputReport writes the output of the errors to a file - for example, a `json` file for use for reporting - * @property {number | boolean=} threads number of worker threads * @property {RegExp | RegExp[]=} resourceQueryExclude Specify the resource query to exclude * @property {string=} configType config type */ diff --git a/types/utils.d.ts b/types/utils.d.ts index 082d8cc..f11e7ce 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -26,17 +26,6 @@ export type EXPECTED_ANY = any; * @returns {ArrifyResult} array of values */ export function arrify(value: T): ArrifyResult; -/** - * @param {string} _ key, but unused - * @param {EXPECTED_ANY} value value - * @returns {{ [x: string]: EXPECTED_ANY }} result - */ -export function jsonStringifyReplacerSortKeys( - _: string, - value: EXPECTED_ANY, -): { - [x: string]: EXPECTED_ANY; -}; /** * @param {string | string[]} files files * @param {string} context context diff --git a/types/worker.d.ts b/types/worker.d.ts deleted file mode 100644 index 15d4d3b..0000000 --- a/types/worker.d.ts +++ /dev/null @@ -1,28 +0,0 @@ -export type ESLint = import("eslint").ESLint; -export type ESLintOptions = import("eslint").ESLint.Options; -export type LintResult = import("eslint").ESLint.LintResult; -export type ESLintClass = { - new (arg0: ESLintOptions): ESLint; - outputFixes: (arg0: LintResult[]) => Promise; -}; -/** - * @param {string | string[]} files files - * @returns {Promise} lint results - */ -export function lintFiles(files: string | string[]): Promise; -/** - * @param {object} options setup worker - * @param {string=} options.eslintPath import path of eslint - * @param {string=} options.configType a config type - * @param {ESLintOptions} options.eslintOptions linter options - * @returns {ESLint} eslint - */ -export function setup({ - eslintPath, - configType, - eslintOptions, -}: { - eslintPath?: string | undefined; - configType?: string | undefined; - eslintOptions: ESLintOptions; -}): ESLint; From ae40a41d02734939cc561690bd1fa51442106737 Mon Sep 17 00:00:00 2001 From: Ricardo Gobbo de Souza Date: Thu, 26 Mar 2026 11:15:11 -0300 Subject: [PATCH 2/2] test: add multithread tests for worker concurrency behavior --- test/threads.test.js | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 test/threads.test.js diff --git a/test/threads.test.js b/test/threads.test.js new file mode 100644 index 0000000..8d66a99 --- /dev/null +++ b/test/threads.test.js @@ -0,0 +1,40 @@ +import workerThreads from "node:worker_threads"; +import pack from "./utils/pack"; + +describe("Multithread", () => { + let workerCount; + let originalWorker; + + beforeEach(() => { + workerCount = 0; + originalWorker = workerThreads.Worker; + workerThreads.Worker = class TrackedWorker extends originalWorker { + constructor(...args) { + super(...args); + workerCount++; + } + }; + }); + + afterEach(() => { + workerThreads.Worker = originalWorker; + }); + + it("should spawn worker threads with concurrency=2", async () => { + const compiler = pack("good", { concurrency: 2 }); + + const stats = await compiler.runAsync(); + + expect(stats.hasErrors()).toBe(false); + expect(workerCount).toBeGreaterThanOrEqual(2); + }); + + it("should not spawn worker threads with concurrency=off", async () => { + const compiler = pack("good", { concurrency: "off" }); + + const stats = await compiler.runAsync(); + + expect(stats.hasErrors()).toBe(false); + expect(workerCount).toBe(0); + }); +});