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

feat: workerIdleMemoryLimit avoids runInBand unless explicitly set #13171

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

### Features

- `[jest-config]` `workerIdleMemoryLimit` now defaults runInBand to false unless an explicit runInBand option is set such as `--runInBand` or `--detectOpenHandles`. Allows for linear test runs with a single worker via `--maxWorkers=1 --workerIdleMemoryLimit=500MB`. ([#13171](https://github.com/facebook/jest/pull/13171))

### Fixes

### Chore & Maintenance
Expand Down
2 changes: 1 addition & 1 deletion docs/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ Lists all test files that Jest will run given the arguments, and exits.

### `--logHeapUsage`

Logs the heap usage after every test. Useful to debug memory leaks. Use together with `--runInBand` and `--expose-gc` in node.
Logs the heap usage after every test. Useful to debug memory leaks. Can be used together with flags like `--runInBand` or `--expose-gc` to diagnose memory leaks, etc.

### `--maxConcurrency=<num>`

Expand Down
12 changes: 12 additions & 0 deletions e2e/__tests__/workerRestarting.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,15 @@ it('all 3 test files should complete', () => {
const {summary} = extractSummary(result.stderr);
expect(summary).toMatchSnapshot();
});

it.each(['runInBand', 'detectOpenHandles'])(
'should warn when used with %s',
arg => {
const result = runJest('worker-restarting', [`--${arg}`]);

expect(result.exitCode).toBe(0);
expect(result.stderr).toMatch(
`workerIdleMemoryLimit does not work in combination with ${arg}`,
);
},
);
27 changes: 27 additions & 0 deletions packages/jest-config/src/__tests__/getMaxWorkers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@

import getMaxWorkers from '../getMaxWorkers';

const logWarnSpy = jest.spyOn(console, 'warn').mockReturnValue(undefined);

jest.mock('os');

beforeEach(() => {
logWarnSpy.mockClear();
});

describe('getMaxWorkers', () => {
beforeEach(() => {
require('os').__setCpus({length: 4});
Expand All @@ -20,6 +26,27 @@ describe('getMaxWorkers', () => {
expect(getMaxWorkers(argv)).toBe(1);
});

it('Returns 1 when runInBand but also logs warning if an argv memory limit is set', () => {
const argv = {runInBand: true, workerIdleMemoryLimit: 6000};
expect(getMaxWorkers(argv)).toBe(1);

expect(logWarnSpy).toHaveBeenCalledTimes(1);
expect(logWarnSpy).toHaveBeenCalledWith(
'workerIdleMemoryLimit does not work in combination with runInBand',
);
});

it('Returns 1 when runInBand but also logs warning if a config memory limit is set', () => {
const argv = {runInBand: true};
const options = {workerIdleMemoryLimit: 6000};
expect(getMaxWorkers(argv, options)).toBe(1);

expect(logWarnSpy).toHaveBeenCalledTimes(1);
expect(logWarnSpy).toHaveBeenCalledWith(
'workerIdleMemoryLimit does not work in combination with runInBand',
);
});

it('Returns 1 when the OS CPUs are not available', () => {
require('os').__setCpus(undefined);
expect(getMaxWorkers({})).toBe(1);
Expand Down
22 changes: 20 additions & 2 deletions packages/jest-config/src/getMaxWorkers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,29 @@ import type {Config} from '@jest/types';

export default function getMaxWorkers(
argv: Partial<
Pick<Config.Argv, 'maxWorkers' | 'runInBand' | 'watch' | 'watchAll'>
Pick<
Config.Argv,
| 'maxWorkers'
| 'runInBand'
| 'watch'
| 'watchAll'
| 'workerIdleMemoryLimit'
>
>,
defaultOptions?: Partial<
Pick<Config.Argv, 'maxWorkers' | 'workerIdleMemoryLimit'>
>,
defaultOptions?: Partial<Pick<Config.Argv, 'maxWorkers'>>,
): number {
const workerIdleMemoryLimit =
argv.workerIdleMemoryLimit ?? defaultOptions?.workerIdleMemoryLimit;

if (argv.runInBand) {
if (workerIdleMemoryLimit) {
console.warn(
'workerIdleMemoryLimit does not work in combination with runInBand',
);
}

return 1;
} else if (argv.maxWorkers) {
return parseWorkers(argv.maxWorkers);
Expand Down
66 changes: 51 additions & 15 deletions packages/jest-core/src/__tests__/testSchedulerHelper.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,63 @@ const getTestMock = () => ({
path: './test/path.js',
});

const logWarnSpy = jest.spyOn(console, 'warn').mockReturnValue(undefined);

const getTestsMock = () => [getTestMock(), getTestMock()];

test.each`
tests | timings | detectOpenHandles | maxWorkers | watch | expectedResult
${[getTestMock()]} | ${[500, 500]} | ${false} | ${undefined} | ${true} | ${true}
${getTestsMock()} | ${[2000, 500]} | ${false} | ${1} | ${true} | ${true}
${getTestsMock()} | ${[2000, 500]} | ${false} | ${2} | ${true} | ${false}
${[getTestMock()]} | ${[2000, 500]} | ${false} | ${undefined} | ${true} | ${false}
${getTestMock()} | ${[500, 500]} | ${false} | ${undefined} | ${true} | ${false}
${getTestsMock()} | ${[2000, 500]} | ${false} | ${1} | ${false} | ${true}
${getTestMock()} | ${[2000, 500]} | ${false} | ${2} | ${false} | ${false}
${[getTestMock()]} | ${[2000]} | ${false} | ${undefined} | ${false} | ${true}
${getTestsMock()} | ${[500, 500]} | ${false} | ${undefined} | ${false} | ${true}
${new Array(45)} | ${[500]} | ${false} | ${undefined} | ${false} | ${false}
${getTestsMock()} | ${[2000, 500]} | ${false} | ${undefined} | ${false} | ${false}
${getTestsMock()} | ${[2000, 500]} | ${true} | ${undefined} | ${false} | ${true}
tests | timings | detectOpenHandles | maxWorkers | watch | workerIdleMemoryLimit | expectedResult
${[getTestMock()]} | ${[500, 500]} | ${false} | ${undefined} | ${true} | ${undefined} | ${true}
${getTestsMock()} | ${[2000, 500]} | ${false} | ${1} | ${true} | ${undefined} | ${true}
${getTestsMock()} | ${[2000, 500]} | ${false} | ${2} | ${true} | ${undefined} | ${false}
${[getTestMock()]} | ${[2000, 500]} | ${false} | ${undefined} | ${true} | ${undefined} | ${false}
${getTestMock()} | ${[500, 500]} | ${false} | ${undefined} | ${true} | ${undefined} | ${false}
${getTestsMock()} | ${[2000, 500]} | ${false} | ${1} | ${false} | ${undefined} | ${true}
${getTestMock()} | ${[2000, 500]} | ${false} | ${2} | ${false} | ${undefined} | ${false}
${[getTestMock()]} | ${[2000]} | ${false} | ${undefined} | ${false} | ${undefined} | ${true}
${getTestsMock()} | ${[500, 500]} | ${false} | ${undefined} | ${false} | ${undefined} | ${true}
${new Array(45)} | ${[500]} | ${false} | ${undefined} | ${false} | ${undefined} | ${false}
${getTestsMock()} | ${[2000, 500]} | ${false} | ${undefined} | ${false} | ${undefined} | ${false}
${getTestsMock()} | ${[2000, 500]} | ${true} | ${undefined} | ${false} | ${undefined} | ${true}
${[getTestMock()]} | ${[500, 500]} | ${false} | ${undefined} | ${true} | ${5000} | ${false}
${getTestsMock()} | ${[2000, 500]} | ${false} | ${1} | ${true} | ${5000} | ${false}
${getTestsMock()} | ${[2000, 500]} | ${false} | ${0} | ${true} | ${5000} | ${false}
${[getTestMock()]} | ${[2000]} | ${false} | ${undefined} | ${false} | ${5000} | ${false}
${getTestsMock()} | ${[500, 500]} | ${false} | ${undefined} | ${false} | ${5000} | ${false}
`(
'shouldRunInBand() - should return $expectedResult for runInBand mode',
({tests, timings, detectOpenHandles, maxWorkers, watch, expectedResult}) => {
({
tests,
timings,
detectOpenHandles,
maxWorkers,
watch,
workerIdleMemoryLimit,
expectedResult,
}) => {
expect(
shouldRunInBand(tests, timings, {detectOpenHandles, maxWorkers, watch}),
shouldRunInBand(tests, timings, {
detectOpenHandles,
maxWorkers,
watch,
workerIdleMemoryLimit,
}),
).toBe(expectedResult);
},
);

test('should log warn for incompatible flags', () => {
expect(
shouldRunInBand(getTestsMock(), [500], {
detectOpenHandles: true,
maxWorkers: 1,
watch: true,
workerIdleMemoryLimit: 5000,
}),
).toBe(true);

expect(logWarnSpy).toHaveBeenCalledTimes(1);
expect(logWarnSpy).toHaveBeenCalledWith(
'workerIdleMemoryLimit does not work in combination with detectOpenHandles',
);
});
16 changes: 15 additions & 1 deletion packages/jest-core/src/testSchedulerHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,25 @@ const SLOW_TEST_TIME = 1000;
export function shouldRunInBand(
tests: Array<Test>,
timings: Array<number>,
{detectOpenHandles, maxWorkers, watch, watchAll}: Config.GlobalConfig,
{
detectOpenHandles,
maxWorkers,
watch,
watchAll,
workerIdleMemoryLimit,
}: Config.GlobalConfig,
): boolean {
// detectOpenHandles makes no sense without runInBand, because it cannot detect leaks in workers
if (detectOpenHandles) {
if (workerIdleMemoryLimit) {
console.warn(
'workerIdleMemoryLimit does not work in combination with detectOpenHandles',
);
}
Comment on lines +26 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah.. Will not work if workerIdleMemoryLimit is set in config file.


return true;
} else if (workerIdleMemoryLimit) {
return false;
}

/*
Expand Down
2 changes: 1 addition & 1 deletion website/versioned_docs/version-25.x/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ Lists all test files that Jest will run given the arguments, and exits.

### `--logHeapUsage`

Logs the heap usage after every test. Useful to debug memory leaks. Use together with `--runInBand` and `--expose-gc` in node.
Logs the heap usage after every test. Useful to debug memory leaks. Can be used together with flags like `--runInBand` or `--expose-gc` to diagnose memory leaks, etc.

### `--maxConcurrency=<num>`

Expand Down
2 changes: 1 addition & 1 deletion website/versioned_docs/version-26.x/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ Lists all test files that Jest will run given the arguments, and exits.

### `--logHeapUsage`

Logs the heap usage after every test. Useful to debug memory leaks. Use together with `--runInBand` and `--expose-gc` in node.
Logs the heap usage after every test. Useful to debug memory leaks. Can be used together with flags like `--runInBand` or `--expose-gc` to diagnose memory leaks, etc.

### `--maxConcurrency=<num>`

Expand Down
2 changes: 1 addition & 1 deletion website/versioned_docs/version-27.x/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ Lists all test files that Jest will run given the arguments, and exits.

### `--logHeapUsage`

Logs the heap usage after every test. Useful to debug memory leaks. Use together with `--runInBand` and `--expose-gc` in node.
Logs the heap usage after every test. Useful to debug memory leaks. Can be used together with flags like `--runInBand` or `--expose-gc` to diagnose memory leaks, etc.

### `--maxConcurrency=<num>`

Expand Down
2 changes: 1 addition & 1 deletion website/versioned_docs/version-28.x/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ Lists all test files that Jest will run given the arguments, and exits.

### `--logHeapUsage`

Logs the heap usage after every test. Useful to debug memory leaks. Use together with `--runInBand` and `--expose-gc` in node.
Logs the heap usage after every test. Useful to debug memory leaks. Can be used together with flags like `--runInBand` or `--expose-gc` to diagnose memory leaks, etc.

### `--maxConcurrency=<num>`

Expand Down
2 changes: 1 addition & 1 deletion website/versioned_docs/version-29.0/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ Lists all test files that Jest will run given the arguments, and exits.

### `--logHeapUsage`

Logs the heap usage after every test. Useful to debug memory leaks. Use together with `--runInBand` and `--expose-gc` in node.
Logs the heap usage after every test. Useful to debug memory leaks. Can be used together with flags like `--runInBand` or `--expose-gc` to diagnose memory leaks, etc.

### `--maxConcurrency=<num>`

Expand Down