Skip to content

Commit a5a27f7

Browse files
authored
feat(jest-worker): add JestWorkerFarm helper type (#12753)
1 parent 9241321 commit a5a27f7

File tree

15 files changed

+202
-52
lines changed

15 files changed

+202
-52
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
### Features
44

5+
- `[jest-worker]` Add `JestWorkerFarm` helper type ([#12753](https://github.com/facebook/jest/pull/12753))
6+
57
### Fixes
68

79
- `[*]` Lower Node 16 requirement to 16.10 from 16.13 due to a [Node bug](https://github.com/nodejs/node/issues/40014) that causes memory and performance issues ([#12754](https://github.com/facebook/jest/pull/12754))

packages/jest-haste-map/src/index.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {Stats, readFileSync, writeFileSync} from 'graceful-fs';
1616
import type {Config} from '@jest/types';
1717
import {escapePathForRegex} from 'jest-regex-util';
1818
import {requireOrImportModule} from 'jest-util';
19-
import {Worker} from 'jest-worker';
19+
import {JestWorkerFarm, Worker} from 'jest-worker';
2020
import HasteFS from './HasteFS';
2121
import HasteModuleMap from './ModuleMap';
2222
import H from './constants';
@@ -108,13 +108,16 @@ type Watcher = {
108108
close(): Promise<void>;
109109
};
110110

111-
type WorkerInterface = {worker: typeof worker; getSha1: typeof getSha1};
111+
type HasteWorker = typeof import('./worker');
112112

113-
export {default as ModuleMap} from './ModuleMap';
114-
export type {SerializableModuleMap} from './types';
115-
export type {IModuleMap} from './types';
116113
export type {default as FS} from './HasteFS';
117-
export type {ChangeEvent, HasteMap as HasteMapObject} from './types';
114+
export {default as ModuleMap} from './ModuleMap';
115+
export type {
116+
ChangeEvent,
117+
HasteMap as HasteMapObject,
118+
IModuleMap,
119+
SerializableModuleMap,
120+
} from './types';
118121

119122
const CHANGE_INTERVAL = 30;
120123
const MAX_WAIT_TIME = 240000;
@@ -216,7 +219,7 @@ export default class HasteMap extends EventEmitter {
216219
private _isWatchmanInstalledPromise: Promise<boolean> | null = null;
217220
private _options: InternalOptions;
218221
private _watchers: Array<Watcher>;
219-
private _worker: WorkerInterface | null;
222+
private _worker: JestWorkerFarm<HasteWorker> | HasteWorker | null;
220223

221224
static getStatic(config: Config.ProjectConfig): HasteMapStatic {
222225
if (config.haste.hasteMapModulePath) {
@@ -690,7 +693,7 @@ export default class HasteMap extends EventEmitter {
690693
this._recoverDuplicates(hasteMap, relativeFilePath, fileMetadata[H.ID]);
691694
}
692695

693-
const promises = [];
696+
const promises: Array<Promise<void>> = [];
694697
for (const relativeFilePath of filesToProcess.keys()) {
695698
if (
696699
this._options.skipPackageJson &&
@@ -726,9 +729,7 @@ export default class HasteMap extends EventEmitter {
726729
private _cleanup() {
727730
const worker = this._worker;
728731

729-
// @ts-expect-error
730-
if (worker && typeof worker.end === 'function') {
731-
// @ts-expect-error
732+
if (worker && 'end' in worker && typeof worker.end === 'function') {
732733
worker.end();
733734
}
734735

@@ -745,19 +746,20 @@ export default class HasteMap extends EventEmitter {
745746
/**
746747
* Creates workers or parses files and extracts metadata in-process.
747748
*/
748-
private _getWorker(options?: {forceInBand: boolean}): WorkerInterface {
749+
private _getWorker(options?: {
750+
forceInBand: boolean;
751+
}): JestWorkerFarm<HasteWorker> | HasteWorker {
749752
if (!this._worker) {
750753
if ((options && options.forceInBand) || this._options.maxWorkers <= 1) {
751754
this._worker = {getSha1, worker};
752755
} else {
753-
// @ts-expect-error: assignment of a worker with custom properties.
754756
this._worker = new Worker(require.resolve('./worker'), {
755757
exposedMethods: ['getSha1', 'worker'],
756758
// @ts-expect-error: option does not exist on the node 12 types
757759
forkOptions: {serialization: 'json'},
758760
maxRetries: 3,
759761
numWorkers: this._options.maxWorkers,
760-
}) as WorkerInterface;
762+
}) as JestWorkerFarm<HasteWorker>;
761763
}
762764
}
763765

packages/jest-reporters/src/CoverageReporter.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@ import type {
2626
} from '@jest/test-result';
2727
import type {Config} from '@jest/types';
2828
import {clearLine, isInteractive} from 'jest-util';
29-
import {Worker} from 'jest-worker';
29+
import {JestWorkerFarm, Worker} from 'jest-worker';
3030
import BaseReporter from './BaseReporter';
3131
import getWatermarks from './getWatermarks';
32-
import type {CoverageWorker, ReporterContext} from './types';
32+
import type {ReporterContext} from './types';
33+
34+
type CoverageWorker = typeof import('./CoverageWorker');
3335

3436
const FAIL_COLOR = chalk.bold.red;
3537
const RUNNING_TEST_COLOR = chalk.bold.dim;
@@ -137,7 +139,9 @@ export default class CoverageReporter extends BaseReporter {
137139
);
138140
}
139141

140-
let worker: CoverageWorker | Worker;
142+
let worker:
143+
| JestWorkerFarm<CoverageWorker>
144+
| typeof import('./CoverageWorker');
141145

142146
if (this._globalConfig.maxWorkers <= 1) {
143147
worker = require('./CoverageWorker');
@@ -148,7 +152,7 @@ export default class CoverageReporter extends BaseReporter {
148152
forkOptions: {serialization: 'json'},
149153
maxRetries: 2,
150154
numWorkers: this._globalConfig.maxWorkers,
151-
});
155+
}) as JestWorkerFarm<CoverageWorker>;
152156
}
153157

154158
const instrumentation = files.map(async fileObj => {

packages/jest-reporters/src/types.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,12 @@ import type {
1313
TestResult,
1414
} from '@jest/test-result';
1515
import type {Config} from '@jest/types';
16-
import type {worker} from './CoverageWorker';
1716

1817
export type ReporterOnStartOptions = {
1918
estimatedTime: number;
2019
showStatus: boolean;
2120
};
2221

23-
export type CoverageWorker = {worker: typeof worker};
24-
2522
export interface Reporter {
2623
readonly onTestResult?: (
2724
test: Test,

packages/jest-runner/src/index.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,11 @@ import type {
1616
} from '@jest/test-result';
1717
import {deepCyclicCopy} from 'jest-util';
1818
import type {TestWatcher} from 'jest-watcher';
19-
import {PromiseWithCustomMessage, Worker} from 'jest-worker';
19+
import {JestWorkerFarm, PromiseWithCustomMessage, Worker} from 'jest-worker';
2020
import runTest from './runTest';
21-
import type {SerializableResolver, worker} from './testWorker';
21+
import type {SerializableResolver} from './testWorker';
2222
import {EmittingTestRunner, TestRunnerOptions, UnsubscribeFn} from './types';
2323

24-
const TEST_WORKER_PATH = require.resolve('./testWorker');
25-
26-
interface WorkerInterface extends Worker {
27-
worker: typeof worker;
28-
}
29-
3024
export type {Test, TestEvents} from '@jest/test-result';
3125
export type {Config} from '@jest/types';
3226
export type {TestWatcher} from 'jest-watcher';
@@ -43,6 +37,8 @@ export type {
4337
UnsubscribeFn,
4438
} from './types';
4539

40+
type TestWorker = typeof import('./testWorker');
41+
4642
export default class TestRunner extends EmittingTestRunner {
4743
readonly #eventEmitter = new Emittery<TestEvents>();
4844

@@ -108,14 +104,14 @@ export default class TestRunner extends EmittingTestRunner {
108104
}
109105
}
110106

111-
const worker = new Worker(TEST_WORKER_PATH, {
107+
const worker = new Worker(require.resolve('./testWorker'), {
112108
exposedMethods: ['worker'],
113109
// @ts-expect-error: option does not exist on the node 12 types
114110
forkOptions: {serialization: 'json', stdio: 'pipe'},
115111
maxRetries: 3,
116112
numWorkers: this._globalConfig.maxWorkers,
117113
setupArgs: [{serializableResolvers: Array.from(resolvers.values())}],
118-
}) as WorkerInterface;
114+
}) as JestWorkerFarm<TestWorker>;
119115

120116
if (worker.getStdout()) worker.getStdout().pipe(process.stdout);
121117
if (worker.getStderr()) worker.getStderr().pipe(process.stderr);
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import {expectError, expectType} from 'tsd-lite';
9+
import type {JestWorkerFarm} from 'jest-worker';
10+
import type * as testWorker from './testWorker';
11+
12+
type TestWorker = {
13+
runTest: () => void;
14+
isResult: boolean;
15+
end: () => void; // reserved keys should be excluded from returned type
16+
getStderr: () => string;
17+
getStdout: () => string;
18+
setup: () => void;
19+
teardown: () => void;
20+
};
21+
22+
// unknown JestWorkerFarm
23+
24+
declare const unknownWorkerFarm: JestWorkerFarm<Record<string, unknown>>;
25+
26+
expectError(unknownWorkerFarm.runTest());
27+
expectError(unknownWorkerFarm.runTestAsync());
28+
29+
expectError(unknownWorkerFarm.getResult());
30+
expectError(unknownWorkerFarm.isResult);
31+
32+
expectError(unknownWorkerFarm.setup());
33+
expectError(unknownWorkerFarm.teardown());
34+
35+
expectType<Promise<{forceExited: boolean}>>(unknownWorkerFarm.end());
36+
expectType<NodeJS.ReadableStream>(unknownWorkerFarm.getStderr());
37+
expectType<NodeJS.ReadableStream>(unknownWorkerFarm.getStdout());
38+
39+
// detected JestWorkerFarm
40+
41+
declare const detectedWorkerFarm: JestWorkerFarm<typeof testWorker>;
42+
43+
expectType<Promise<void>>(detectedWorkerFarm.runTest());
44+
expectType<Promise<void>>(detectedWorkerFarm.runTestAsync());
45+
46+
expectError(detectedWorkerFarm.getResult());
47+
expectError(detectedWorkerFarm.isResult);
48+
49+
expectError(detectedWorkerFarm.setup());
50+
expectError(detectedWorkerFarm.teardown());
51+
52+
expectError<Promise<void>>(detectedWorkerFarm.end());
53+
expectType<Promise<{forceExited: boolean}>>(detectedWorkerFarm.end());
54+
55+
expectError<Promise<string>>(detectedWorkerFarm.getStderr());
56+
expectType<NodeJS.ReadableStream>(detectedWorkerFarm.getStderr());
57+
58+
expectError<Promise<string>>(detectedWorkerFarm.getStdout());
59+
expectType<NodeJS.ReadableStream>(detectedWorkerFarm.getStdout());
60+
61+
// typed JestWorkerFarm
62+
63+
declare const typedWorkerFarm: JestWorkerFarm<TestWorker>;
64+
65+
expectType<Promise<void>>(typedWorkerFarm.runTest());
66+
67+
expectError(typedWorkerFarm.isResult);
68+
expectError(typedWorkerFarm.runTestAsync());
69+
70+
expectError(typedWorkerFarm.setup());
71+
expectError(typedWorkerFarm.teardown());
72+
73+
expectError<Promise<void>>(typedWorkerFarm.end());
74+
expectType<Promise<{forceExited: boolean}>>(typedWorkerFarm.end());
75+
76+
expectError<Promise<string>>(typedWorkerFarm.getStderr());
77+
expectType<NodeJS.ReadableStream>(typedWorkerFarm.getStderr());
78+
79+
expectError<Promise<string>>(typedWorkerFarm.getStdout());
80+
expectType<NodeJS.ReadableStream>(typedWorkerFarm.getStdout());
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
export function runTest(): void {}
9+
export async function runTestAsync(): Promise<void> {}
10+
11+
function getResult(): string {
12+
return 'result';
13+
}
14+
export const isResult = true;
15+
16+
// reserved keys should be excluded from returned type
17+
18+
export function end(): void {}
19+
export function getStderr(): string {
20+
return 'get-err';
21+
}
22+
export function getStdout(): string {
23+
return 'get-out';
24+
}
25+
export function setup(): void {}
26+
export function teardown(): void {}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"extends": "../../../tsconfig.json",
3+
"compilerOptions": {
4+
"composite": false,
5+
"noUnusedLocals": false,
6+
"noUnusedParameters": false,
7+
"skipLibCheck": true,
8+
9+
"types": ["node"]
10+
},
11+
"include": ["./**/*"]
12+
}

packages/jest-worker/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@
2222
"supports-color": "^8.0.0"
2323
},
2424
"devDependencies": {
25+
"@tsd/typescript": "~4.6.2",
2526
"@types/merge-stream": "^1.1.2",
2627
"@types/supports-color": "^8.1.0",
2728
"get-stream": "^6.0.0",
2829
"jest-leak-detector": "^28.0.1",
30+
"tsd-lite": "^0.5.1",
2931
"worker-farm": "^1.6.0"
3032
},
3133
"engines": {

packages/jest-worker/src/Farm.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,20 @@ import FifoQueue from './FifoQueue';
99
import {
1010
CHILD_MESSAGE_CALL,
1111
ChildMessage,
12-
FarmOptions,
1312
OnCustomMessage,
1413
OnEnd,
1514
OnStart,
1615
PromiseWithCustomMessage,
1716
QueueChildMessage,
1817
TaskQueue,
1918
WorkerCallback,
19+
WorkerFarmOptions,
2020
WorkerInterface,
2121
WorkerSchedulingPolicy,
2222
} from './types';
2323

2424
export default class Farm {
25-
private readonly _computeWorkerKey: FarmOptions['computeWorkerKey'];
25+
private readonly _computeWorkerKey: WorkerFarmOptions['computeWorkerKey'];
2626
private readonly _workerSchedulingPolicy: WorkerSchedulingPolicy;
2727
private readonly _cacheKeys: Record<string, WorkerInterface> =
2828
Object.create(null);
@@ -33,7 +33,7 @@ export default class Farm {
3333
constructor(
3434
private _numOfWorkers: number,
3535
private _callback: WorkerCallback,
36-
options: FarmOptions = {},
36+
options: WorkerFarmOptions = {},
3737
) {
3838
this._computeWorkerKey = options.computeWorkerKey;
3939
this._workerSchedulingPolicy =

0 commit comments

Comments
 (0)