diff --git a/e2e/__tests__/__snapshots__/workerRestartBeforeSend.test.ts.snap b/e2e/__tests__/__snapshots__/workerRestartBeforeSend.test.ts.snap new file mode 100644 index 000000000000..33a283aa2937 --- /dev/null +++ b/e2e/__tests__/__snapshots__/workerRestartBeforeSend.test.ts.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`all 3 test files should complete 1`] = ` +"Test Suites: 1 failed, 2 passed, 3 total +Tests: 2 passed, 2 total +Snapshots: 0 total +Time: <> +Ran all test suites." +`; diff --git a/e2e/__tests__/workerRestartBeforeSend.test.ts b/e2e/__tests__/workerRestartBeforeSend.test.ts new file mode 100644 index 000000000000..0d8aaf05c945 --- /dev/null +++ b/e2e/__tests__/workerRestartBeforeSend.test.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {extractSummary} from '../Utils'; +import runJest from '../runJest'; + +it('all 3 test files should complete', () => { + const result = runJest('worker-restart-before-send'); + const {summary} = extractSummary(result.stderr); + expect(summary).toMatchSnapshot(); +}); diff --git a/e2e/worker-restart-before-send/__tests__/test1.js b/e2e/worker-restart-before-send/__tests__/test1.js new file mode 100644 index 000000000000..a29f99a81bd5 --- /dev/null +++ b/e2e/worker-restart-before-send/__tests__/test1.js @@ -0,0 +1,15 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +test('jest-worker killed', async () => { + await new Promise(resolve => setTimeout(resolve, 100)); +}); + +setTimeout(() => { + // Self-kill process. + process.kill(process.pid); +}, 50); diff --git a/e2e/worker-restart-before-send/__tests__/test2.js b/e2e/worker-restart-before-send/__tests__/test2.js new file mode 100644 index 000000000000..95a24306a6ba --- /dev/null +++ b/e2e/worker-restart-before-send/__tests__/test2.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +test('basic test', async () => { + await new Promise(resolve => setTimeout(resolve, 100)); +}); diff --git a/e2e/worker-restart-before-send/__tests__/test3.js b/e2e/worker-restart-before-send/__tests__/test3.js new file mode 100644 index 000000000000..95a24306a6ba --- /dev/null +++ b/e2e/worker-restart-before-send/__tests__/test3.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +test('basic test', async () => { + await new Promise(resolve => setTimeout(resolve, 100)); +}); diff --git a/e2e/worker-restart-before-send/package.json b/e2e/worker-restart-before-send/package.json new file mode 100644 index 000000000000..4df13823d89a --- /dev/null +++ b/e2e/worker-restart-before-send/package.json @@ -0,0 +1,6 @@ +{ + "jest": { + "maxWorkers": 2, + "testSequencer": "./testSequencer.js" + } +} diff --git a/e2e/worker-restart-before-send/testSequencer.js b/e2e/worker-restart-before-send/testSequencer.js new file mode 100644 index 000000000000..e5a470ed49fe --- /dev/null +++ b/e2e/worker-restart-before-send/testSequencer.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +const Sequencer = require('@jest/test-sequencer').default; + +// Ensure that test1.js runs first. +class CustomSequencer extends Sequencer { + sort(tests) { + // Test structure information + // https://github.com/facebook/jest/blob/6b8b1404a1d9254e7d5d90a8934087a9c9899dab/packages/jest-runner/src/types.ts#L17-L21 + const copyTests = Array.from(tests); + return copyTests.sort((testA, testB) => (testA.path > testB.path ? 1 : -1)); + } +} + +module.exports = CustomSequencer; diff --git a/packages/jest-worker/src/WorkerPool.ts b/packages/jest-worker/src/WorkerPool.ts index 4ac1da31c7a1..baa5f1cb43c7 100644 --- a/packages/jest-worker/src/WorkerPool.ts +++ b/packages/jest-worker/src/WorkerPool.ts @@ -24,6 +24,7 @@ class WorkerPool extends BaseWorkerPool implements WorkerPoolInterface { onEnd: OnEnd, onCustomMessage: OnCustomMessage, ): void { + this.restartWorkerIfShutDown(workerId); this.getWorkerById(workerId).send(request, onStart, onEnd, onCustomMessage); } diff --git a/packages/jest-worker/src/base/BaseWorkerPool.ts b/packages/jest-worker/src/base/BaseWorkerPool.ts index 92896aab5b2c..c26d7b4b1ee3 100644 --- a/packages/jest-worker/src/base/BaseWorkerPool.ts +++ b/packages/jest-worker/src/base/BaseWorkerPool.ts @@ -13,6 +13,7 @@ import { WorkerInterface, WorkerOptions, WorkerPoolOptions, + WorkerStates, } from '../types'; // How long to wait for the child process to terminate @@ -28,9 +29,11 @@ export default class BaseWorkerPool { private readonly _stdout: NodeJS.ReadableStream; protected readonly _options: WorkerPoolOptions; private readonly _workers: Array; + private readonly _workerPath: string; constructor(workerPath: string, options: WorkerPoolOptions) { this._options = options; + this._workerPath = workerPath; this._workers = new Array(options.numWorkers); const stdout = mergeStream(); @@ -84,6 +87,24 @@ export default class BaseWorkerPool { return this._workers[workerId]; } + restartWorkerIfShutDown(workerId: number): void { + if (this._workers[workerId].state === WorkerStates.SHUT_DOWN) { + const {forkOptions, maxRetries, resourceLimits, setupArgs} = + this._options; + const workerOptions: WorkerOptions = { + forkOptions, + idleMemoryLimit: this._options.idleMemoryLimit, + maxRetries, + resourceLimits, + setupArgs, + workerId, + workerPath: this._workerPath, + }; + const worker = this.createWorker(workerOptions); + this._workers[workerId] = worker; + } + } + createWorker(_workerOptions: WorkerOptions): WorkerInterface { throw Error('Missing method createWorker in WorkerPool'); }