Skip to content

Commit

Permalink
Allow the cli to use a string for --maxWorkers (#8565)
Browse files Browse the repository at this point in the history
  • Loading branch information
philiiiiiipp authored and SimenB committed Jun 24, 2019
1 parent 5d42d0a commit b644d85
Show file tree
Hide file tree
Showing 11 changed files with 62 additions and 21 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -8,9 +8,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
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
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 ' +
'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
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
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
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
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
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
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
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
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

0 comments on commit b644d85

Please sign in to comment.