Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Restart a shut down worker before sending it a task. #14015

Merged
merged 6 commits into from Apr 19, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -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: <<REPLACED>>
Ran all test suites."
`;
15 changes: 15 additions & 0 deletions 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();
});
31 changes: 31 additions & 0 deletions e2e/snapshot/__tests__/__snapshots__/snapshot.test.js.snap
@@ -0,0 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`snapshot is not influenced by previous counter 1`] = `
{
"a": 43,
"b": "43",
"c": "fortythree",
}
`;

exports[`snapshot works with \\r\\n 1`] = `
"<div>
</div>"
`;

exports[`snapshot works with plain objects and the title has \`escape\` characters 1`] = `
{
"a": 1,
"b": "2",
"c": "three\`",
}
`;

exports[`snapshot works with plain objects and the title has \`escape\` characters 2`] = `
{
"a": 1,
"b": "2",
"c": "three\`",
"d": "4",
}
`;
31 changes: 31 additions & 0 deletions e2e/snapshot/__tests__/__snapshots__/snapshot.test_copy.js.snap
@@ -0,0 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`snapshot is not influenced by previous counter 1`] = `
{
"a": 43,
"b": "43",
"c": "fortythree",
}
`;

exports[`snapshot works with \\r\\n 1`] = `
"<div>
</div>"
`;

exports[`snapshot works with plain objects and the title has \`escape\` characters 1`] = `
{
"a": 1,
"b": "2",
"c": "three\`",
}
`;

exports[`snapshot works with plain objects and the title has \`escape\` characters 2`] = `
{
"a": 1,
"b": "2",
"c": "three\`",
"d": "4",
}
`;
41 changes: 41 additions & 0 deletions e2e/snapshot/__tests__/snapshot.test_copy.js
@@ -0,0 +1,41 @@
/**
* 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';

describe('snapshot', () => {
PeteTheHeat marked this conversation as resolved.
Show resolved Hide resolved
it('works with plain objects and the title has `escape` characters', () => {
const test = {
a: 1,
b: '2',
c: 'three`',
};
expect(test).toMatchSnapshot();
test.d = '4';
expect(test).toMatchSnapshot();
});

it('is not influenced by previous counter', () => {
const test = {
a: 43,
b: '43',
c: 'fortythree',
};
expect(test).toMatchSnapshot();
});

it('cannot be used with .not', () => {
expect(() => expect('').not.toMatchSnapshot()).toThrow(
'Snapshot matchers cannot be used with not',
);
});

// Issue reported here: https://github.com/facebook/jest/issues/2969
it('works with \\r\\n', () => {
expect('<div>\r\n</div>').toMatchSnapshot();
});
});
18 changes: 18 additions & 0 deletions e2e/to-match-inline-snapshot/__tests__/custom-matchers.test.js
@@ -0,0 +1,18 @@
/**
* 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 {toMatchInlineSnapshot} = require('jest-snapshot');
expect.extend({
toMatchCustomInlineSnapshot(received, ...args) {
return toMatchInlineSnapshot.call(this, received, ...args);
},
});
test('inline snapshots', () => {
expect({apple: 'original value'}).toMatchCustomInlineSnapshot();
});
@@ -0,0 +1,12 @@
/**
* 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';

test('accepts custom snapshot name', () => {
expect(true).toMatchSnapshot('custom-name');
});
15 changes: 15 additions & 0 deletions 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);
10 changes: 10 additions & 0 deletions 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));
});
10 changes: 10 additions & 0 deletions 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));
});
6 changes: 6 additions & 0 deletions e2e/worker-restart-before-send/package.json
@@ -0,0 +1,6 @@
{
"jest": {
"maxWorkers": 2,
"testSequencer": "./testSequencer.js"
}
}
22 changes: 22 additions & 0 deletions 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;
1 change: 1 addition & 0 deletions packages/jest-worker/src/WorkerPool.ts
Expand Up @@ -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);
}

Expand Down
21 changes: 21 additions & 0 deletions packages/jest-worker/src/base/BaseWorkerPool.ts
Expand Up @@ -13,6 +13,7 @@ import {
WorkerInterface,
WorkerOptions,
WorkerPoolOptions,
WorkerStates,
} from '../types';

// How long to wait for the child process to terminate
Expand All @@ -28,9 +29,11 @@ export default class BaseWorkerPool {
private readonly _stdout: NodeJS.ReadableStream;
protected readonly _options: WorkerPoolOptions;
private readonly _workers: Array<WorkerInterface>;
private readonly _workerPath: string;

constructor(workerPath: string, options: WorkerPoolOptions) {
this._options = options;
this._workerPath = workerPath;
this._workers = new Array(options.numWorkers);

const stdout = mergeStream();
Expand Down Expand Up @@ -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');
}
Expand Down