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

Allow the cli to use a string for --maxWorkers #8565

Merged
merged 6 commits into from
Jun 24, 2019
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -7,9 +7,11 @@
- `[jest-cli]` Improve chai support (with detailed output, to match jest exceptions) ([#8454](https://github.com/facebook/jest/pull/8454))
- `[*]` Manage the global timeout with `--testTimeout` command line argument. ([#8456](https://github.com/facebook/jest/pull/8456))
- `[pretty-format]` Render custom displayName of memoized components
- `[jest-validate]` Allow `maxWorkers` as part of the `jest.config.js` ([#8565](https://github.com/facebook/jest/pull/8565))

### Fixes

- `[jest-cli]` Allow `--maxWorkers` to work with % input again ([#8565](https://github.com/facebook/jest/pull/8565))
- `[babel-plugin-jest-hoist]` Expand list of whitelisted globals in global mocks ([#8429](https://github.com/facebook/jest/pull/8429)
- `[jest-core]` Make watch plugin initialization errors look nice ([#8422](https://github.com/facebook/jest/pull/8422))
- `[jest-snapshot]` Prevent inline snapshots from drifting when inline snapshots are updated ([#8492](https://github.com/facebook/jest/pull/8492))
Expand Down
7 changes: 6 additions & 1 deletion packages/jest-cli/src/__tests__/cli/args.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,15 @@ describe('check', () => {
it('raises an exception if maxWorkers is specified with no number', () => {
const argv = ({maxWorkers: undefined} as unknown) as Config.Argv;
expect(() => check(argv)).toThrow(
'The --maxWorkers (-w) option requires a number to be specified',
'The --maxWorkers (-w) option requires a number or string to be specified',
);
});

it('allows maxWorkers to be a %', () => {
const argv = ({maxWorkers: '50%'} as unknown) as Config.Argv;
expect(() => check(argv)).not.toThrow();
});

it('raises an exception if config is not a valid JSON string', () => {
const argv = {config: 'x:1'} as Config.Argv;
expect(() => check(argv)).toThrow(
Expand Down
5 changes: 3 additions & 2 deletions packages/jest-cli/src/cli/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ export const check = (argv: Config.Argv) => {

if (argv.hasOwnProperty('maxWorkers') && argv.maxWorkers === undefined) {
throw new Error(
'The --maxWorkers (-w) option requires a number to be specified.\n' +
'The --maxWorkers (-w) option requires a number or string to be specified.\n' +
'Example usage: jest --maxWorkers 2\n' +
'Example usage: jest --maxWorkers 50%\n' +
'Or did you mean --watch?',
);
}
Expand Down Expand Up @@ -349,7 +350,7 @@ export const options = {
'will spawn for running tests. This defaults to the number of the ' +
philiiiiiipp marked this conversation as resolved.
Show resolved Hide resolved
'cores available on your machine. (its usually best not to override ' +
'this default)',
type: 'number' as 'number',
type: 'string' as 'string',
},
moduleDirectories: {
description:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ module.exports = {
// A set of global variables that need to be available in all test environments
// globals: {},

// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// maxWorkers: \\"50%\\",

// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: [
// \\"node_modules\\"
Expand Down
1 change: 1 addition & 0 deletions packages/jest-config/src/Defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const defaultOptions: Config.DefaultOptions = {
throwOnModuleCollision: false,
},
maxConcurrency: 5,
maxWorkers: '50%',
moduleDirectories: ['node_modules'],
moduleFileExtensions: ['js', 'json', 'jsx', 'ts', 'tsx', 'node'],
moduleNameMapper: {},
Expand Down
2 changes: 2 additions & 0 deletions packages/jest-config/src/Descriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ const descriptions: {[key in keyof Config.InitialOptions]: string} = {
'A path to a module which exports an async function that is triggered once after all test suites',
globals:
'A set of global variables that need to be available in all test environments',
maxWorkers:
'The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.',
moduleDirectories:
"An array of directory names to be searched recursively up from the requiring module's location",
moduleFileExtensions: 'An array of file extensions your modules use',
Expand Down
1 change: 1 addition & 0 deletions packages/jest-config/src/ValidConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const initialOptions: Config.InitialOptions = {
lastCommit: false,
logHeapUsage: true,
maxConcurrency: 5,
maxWorkers: '50%',
moduleDirectories: ['node_modules'],
moduleFileExtensions: ['js', 'json', 'jsx', 'ts', 'tsx', 'node'],
moduleLoader: '<rootDir>',
Expand Down
37 changes: 21 additions & 16 deletions packages/jest-config/src/getMaxWorkers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,34 @@ import {Config} from '@jest/types';

export default function getMaxWorkers(
argv: Partial<Pick<Config.Argv, 'maxWorkers' | 'runInBand' | 'watch'>>,
defaultOptions?: Partial<Pick<Config.Argv, 'maxWorkers'>>,
): number {
if (argv.runInBand) {
return 1;
} else if (argv.maxWorkers) {
// TODO: How to type this properly? Should probably use `coerce` from `yargs`
const maxWorkers = argv.maxWorkers;
const parsed = parseInt(maxWorkers as string, 10);

if (
typeof maxWorkers === 'string' &&
maxWorkers.trim().endsWith('%') &&
parsed > 0 &&
parsed <= 100
) {
const cpus = os.cpus().length;
const workers = Math.floor((parsed / 100) * cpus);
return workers >= 1 ? workers : 1;
}

return parsed > 0 ? parsed : 1;
return parseWorkers(argv.maxWorkers);
} else if (defaultOptions && defaultOptions.maxWorkers) {
return parseWorkers(defaultOptions.maxWorkers);
} else {
// In watch mode, Jest should be unobtrusive and not use all available CPUs.
const cpus = os.cpus() ? os.cpus().length : 1;
return Math.max(argv.watch ? Math.floor(cpus / 2) : cpus - 1, 1);
}
}

const parseWorkers = (maxWorkers: string | number): number => {
const parsed = parseInt(maxWorkers.toString(), 10);

if (
typeof maxWorkers === 'string' &&
maxWorkers.trim().endsWith('%') &&
parsed > 0 &&
parsed <= 100
) {
const cpus = os.cpus().length;
const workers = Math.floor((parsed / 100) * cpus);
return workers >= 1 ? workers : 1;
}

return parsed > 0 ? parsed : 1;
};
2 changes: 1 addition & 1 deletion packages/jest-config/src/normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -928,7 +928,7 @@ export default function normalize(
(newOptions.maxConcurrency as unknown) as string,
10,
);
newOptions.maxWorkers = getMaxWorkers(argv);
newOptions.maxWorkers = getMaxWorkers(argv, options);

if (newOptions.testRegex!.length && options.testMatch) {
throw createConfigError(
Expand Down
2 changes: 2 additions & 0 deletions packages/jest-types/src/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export type DefaultOptions = {
globalSetup: string | null | undefined;
globalTeardown: string | null | undefined;
haste: HasteConfig;
maxWorkers: number | string;
maxConcurrency: number;
moduleDirectories: Array<string>;
moduleFileExtensions: Array<string>;
Expand Down Expand Up @@ -156,6 +157,7 @@ export type InitialOptions = {
listTests?: boolean;
mapCoverage?: boolean;
maxConcurrency?: number;
maxWorkers: number | string;
moduleDirectories?: Array<string>;
moduleFileExtensions?: Array<string>;
moduleLoader?: Path;
Expand Down
21 changes: 20 additions & 1 deletion packages/jest-validate/src/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
*/

import {ValidationOptions} from './types';

import defaultConfig from './defaultConfig';
import {ValidationError} from './utils';

let hasDeprecationWarnings = false;

Expand Down Expand Up @@ -46,6 +46,21 @@ const _validate = (
);

hasDeprecationWarnings = hasDeprecationWarnings || isDeprecatedKey;
} else if (allowsMultipleTypes(key)) {
const value = config[key];

if (
typeof options.condition === 'function' &&
typeof options.error === 'function'
) {
if (key === 'maxWorkers' && !isOfTypeStringOrNumber(value)) {
throw new ValidationError(
'Validation Error',
`${key} has to be of type string or number`,
`maxWorkers=50% or\nmaxWorkers=3`,
);
}
}
} else if (Object.hasOwnProperty.call(exampleConfig, key)) {
if (
typeof options.condition === 'function' &&
Expand Down Expand Up @@ -76,6 +91,10 @@ const _validate = (
return {hasDeprecationWarnings};
};

const allowsMultipleTypes = (key: string): boolean => key === 'maxWorkers';
const isOfTypeStringOrNumber = (value: any): boolean =>
typeof value === 'number' || typeof value === 'string';

const validate = (config: Record<string, any>, options: ValidationOptions) => {
hasDeprecationWarnings = false;

Expand Down