From db0989deaf8cfab342e150e0d2bc2ed3af0c7fdd Mon Sep 17 00:00:00 2001 From: Nicolas DUBIEN Date: Wed, 30 Sep 2026 23:38:56 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=A5(worker)=20Move=20to=20plugins=20fo?= =?UTF-8?q?r=20worker-spawn=20(#7383)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Plugins are there from 4.10.0, they are supposed to replace all previously existing life-cycle methods including `beforeEach` and `afterEach` which were heavily leveraged by the worker package. The proposed change in imperfect for several reasons: - The property can't run with the default asserter - The plugin has to come first in many cases, notable when using timeouts but nothing can do it (yet) if timeout plugin is instantiated in a global fashion ## Checklist — _Don't delete this checklist and make sure you do the following before opening the PR_ - [ ] I have a full understanding of every line in this PR — whether the code was hand-written, AI-generated (our [AI Policy](https://github.com/dubzzz/fast-check/blob/main/AI_POLICY.md)), copied from external sources or produced by any other tool - [ ] I flagged the impact of my change (minor / patch / major) either by running `pnpm run bump` or by following the instructions from the changeset bot - [ ] I kept this PR focused on a single concern and did not bundle unrelated changes - [ ] I followed the [gitmoji](https://gitmoji.dev/) specification for the name of the PR, including the package scope (e.g. `🐛(vitest) Something...`) when the change targets a package other than `fast-check` - [ ] I added relevant tests and they would have failed without my PR (when applicable) --- .changeset/proud-cloths-drive.md | 5 +++ packages/worker/package.json | 2 +- .../worker/src/internals/MainThreadRunner.ts | 37 +++++++++++-------- packages/worker/src/main.ts | 17 +++++++-- pnpm-lock.yaml | 2 +- 5 files changed, 42 insertions(+), 21 deletions(-) create mode 100644 .changeset/proud-cloths-drive.md diff --git a/.changeset/proud-cloths-drive.md b/.changeset/proud-cloths-drive.md new file mode 100644 index 000000000..4057183d5 --- /dev/null +++ b/.changeset/proud-cloths-drive.md @@ -0,0 +1,5 @@ +--- +"@fast-check/worker": minor +--- + +💥(worker) Move to plugins for worker-spawn diff --git a/packages/worker/package.json b/packages/worker/package.json index c2e128922..319c70a47 100644 --- a/packages/worker/package.json +++ b/packages/worker/package.json @@ -30,7 +30,7 @@ }, "homepage": "https://github.com/dubzzz/fast-check/tree/main/packages/worker#readme", "dependencies": { - "fast-check": "^4.0.0", + "fast-check": "^4.10.0", "pure-rand": "^8.0.0" }, "devDependencies": { diff --git a/packages/worker/src/internals/MainThreadRunner.ts b/packages/worker/src/internals/MainThreadRunner.ts index 902571687..a549e6533 100644 --- a/packages/worker/src/internals/MainThreadRunner.ts +++ b/packages/worker/src/internals/MainThreadRunner.ts @@ -5,7 +5,8 @@ import type { IWorkerPool, Payload, PooledWorker } from './worker-pool/IWorkerPo import { OneTimePool } from './worker-pool/OneTimePool.js'; import { GlobalPool } from './worker-pool/GlobalPool.js'; import { buildWorkerProperty } from './worker-property/WorkerPropertyBuilder.js'; -import { PreconditionFailure } from 'fast-check'; +import { PreconditionFailure, beforeEach } from 'fast-check'; +import type { UniversalPlugin } from 'fast-check'; /** * Create a property able to run in the main thread and firing workers whenever required @@ -22,7 +23,11 @@ export function runMainThread( isolationLevel: 'file' | 'property' | 'predicate', randomSource: 'main-thread' | 'worker', arbitraries: PropertyArbitraries, -): { property: WorkerProperty; terminateAllWorkers: () => Promise } { +): { + property: WorkerProperty; + workerPlugin: UniversalPlugin; + terminateAllWorkers: () => Promise; +} { const lock = new Lock(); const pool: IWorkerPool> = isolationLevel === 'predicate' ? new OneTimePool(workerFileUrl) @@ -47,23 +52,23 @@ export function runMainThread( }, randomSource === 'worker', ); - property.beforeEach(async (hookFunction) => { - await hookFunction(); // run outside of the worker, can throw + const workerPlugin = beforeEach(async () => { + // NOTE: Being capable of enriching the context of the runner with extra pieces + // would make us capable of forwarding the worker variable safely to the runner const acquired = await lock.acquire(); releaseLock = acquired.release; worker = pool.getFirstAvailableWorker() || (await pool.spawnNewWorker()); // can throw - }); - property.afterEach(async (hookFunction) => { - if (worker !== undefined) { - worker.terminateIfStillRunning().catch(() => void 0); // no need to wait for the termination - worker = undefined; - } - if (releaseLock !== undefined) { - releaseLock(); - releaseLock = undefined; - } - await hookFunction(); // run outside of the worker, can throw + return () => { + if (worker !== undefined) { + worker.terminateIfStillRunning().catch(() => void 0); // no need to wait for the termination + worker = undefined; + } + if (releaseLock !== undefined) { + releaseLock(); + releaseLock = undefined; + } + }; }); const terminateAllWorkers = () => pool.terminateAllWorkers(); - return { property, terminateAllWorkers }; + return { property, workerPlugin, terminateAllWorkers }; } diff --git a/packages/worker/src/main.ts b/packages/worker/src/main.ts index 5e3be4bb0..fa9aa3d34 100644 --- a/packages/worker/src/main.ts +++ b/packages/worker/src/main.ts @@ -1,7 +1,7 @@ import { isMainThread, parentPort, workerData } from 'node:worker_threads'; import { assert as fcAssert, asyncProperty as fcProperty } from 'fast-check'; -import type { Property, Parameters } from 'fast-check'; +import type { Property, Parameters, UniversalPlugin } from 'fast-check'; import { runWorker } from './internals/worker-runner/WorkerRunner.js'; import { runMainThread } from './internals/MainThreadRunner.js'; import { NoopWorkerProperty } from './internals/worker-property/NoopWorkerProperty.js'; @@ -19,6 +19,8 @@ async function clearAllWorkersFor(property: Property): Promise { await terminateAllWorkers(); } +const allKnownWorkerPluginPerProperty = new Map, UniversalPlugin>(); + /** * Run the property, throw in case of failure. * @@ -37,7 +39,15 @@ export async function assert(property: Property, params?: Parameters if (isMainThread) { // Main thread code try { - await fcAssert(property, params); + const workerPlugin = allKnownWorkerPluginPerProperty.get(property); + const refinedParams: typeof params = + workerPlugin !== undefined + ? { + ...params, + plugins: [workerPlugin, ...(params !== undefined && params.plugins !== undefined ? params.plugins : [])], + } + : params; + await fcAssert(property, refinedParams); } finally { await clearAllWorkersFor(property); } @@ -93,7 +103,7 @@ function workerProperty( const isolationLevel = options.isolationLevel || 'file'; const randomSource = options.randomSource || 'main-thread'; const arbitraries = args.slice(0, -1) as PropertyArbitraries; - const { property, terminateAllWorkers } = runMainThread( + const { property, workerPlugin, terminateAllWorkers } = runMainThread( url, currentPredicateId, isolationLevel, @@ -101,6 +111,7 @@ function workerProperty( arbitraries, ); allKnownTerminateAllWorkersPerProperty.set(property, terminateAllWorkers); + allKnownWorkerPluginPerProperty.set(property, workerPlugin); return property; } else if (parentPort !== null && workerData.fastcheckWorker === true) { // Worker code diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 07cf27eb4..ac3a0deab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -446,7 +446,7 @@ importers: packages/worker: dependencies: fast-check: - specifier: ^4.0.0 + specifier: ^4.10.0 version: link:../fast-check pure-rand: specifier: ^8.0.0 -- knotmirror 0.0.1