Skip to content

Commit

Permalink
feat: implement --shard option jestjs#6270
Browse files Browse the repository at this point in the history
  • Loading branch information
marionebl committed Mar 3, 2022
1 parent 3f3aa80 commit 6641e73
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 0 deletions.
11 changes: 11 additions & 0 deletions docs/CLI.md
Expand Up @@ -338,6 +338,17 @@ Run only the tests of the specified projects. Jest uses the attribute `displayNa

A list of paths to modules that run some code to configure or to set up the testing framework before each test. Beware that files imported by the setup scripts will not be mocked during testing.

### `--shard`

Shard suite to execute in on multiple machines. For example, to split the suite into three shards, each running one third of the tests:

```
jest --shard=1/3
jest --shard=2/3
jest --shard=3/3
```


### `--showConfig`

Print your Jest config and then exits.
Expand Down
27 changes: 27 additions & 0 deletions packages/jest-cli/src/cli/args.ts
Expand Up @@ -87,6 +87,27 @@ export function check(argv: Config.Argv): true {
);
}

if (argv.shard) {
const shardPair = argv?.shard
.split('/')
.map(parseInt)
.filter((shard: number) => typeof shard === 'number' && !Number.isNaN(shard));

if (shardPair.length !== 2) {
throw new Error(`The --shard option requires a string in the format of <n>/<m>.\n Example usage jest --shard=1/5`);
}

const [shardIndex, shardCount] = shardPair;

if (shardIndex === 0 || shardCount === 0) {
throw new Error(`The --shard option requires 1-based values, received 0 in the pair.\n Example usage jest --shard=1/5`);
}

if (shardIndex > shardCount) {
throw new Error(`The --shard option <n>/<m> requires <n> to be lower or equal than <m>.\n Example usage jest --shard=1/5`);
}
}

return true;
}

Expand Down Expand Up @@ -521,6 +542,12 @@ export const options = {
string: true,
type: 'array',
},
shard: {
description:
'Shard tests and execute only the selected shard, specify in ' +
'the form "current/all". 1-based, for example "3/5"',
type: 'string'
},
showConfig: {
description: 'Print your jest config and then exits.',
type: 'boolean',
Expand Down
14 changes: 14 additions & 0 deletions packages/jest-config/src/normalize.ts
Expand Up @@ -1220,6 +1220,20 @@ export default async function normalize(
newOptions.logHeapUsage = false;
}

if (argv.shard) {
const [shardIndex, shardCount] = argv?.shard
.split('/')
.map(parseInt)
.filter(
(shard: number) => typeof shard === 'number' && !Number.isNaN(shard),
);

newOptions.shard = {
shardCount,
shardIndex,
};
}

return {
hasDeprecationWarnings,
options: newOptions,
Expand Down
1 change: 1 addition & 0 deletions packages/jest-core/src/runJest.ts
Expand Up @@ -192,6 +192,7 @@ export default async function runJest({
}),
);

allTests = sequencer.shard?.(allTests, globalConfig.shard) ?? allTests;
allTests = await sequencer.sort(allTests);

if (globalConfig.listTests) {
Expand Down
22 changes: 22 additions & 0 deletions packages/jest-test-sequencer/src/index.ts
Expand Up @@ -17,6 +17,11 @@ type Cache = {
[key: string]: [0 | 1, number];
};

export type SortOptions = {
shardIndex: number;
shardCount: number;
};

/**
* The TestSequencer will ultimately decide which tests should run first.
* It is responsible for storing and reading from a local cache
Expand Down Expand Up @@ -65,6 +70,23 @@ export default class TestSequencer {
return cache;
}

shard(
tests: Array<Test>,
options: SortOptions = {shardCount: 1, shardIndex: 1},
): Array<Test> {
if (options?.shardCount > 1) {
const shardSize = Math.ceil(tests.length / options.shardCount);
const shardStart = shardSize * options.shardIndex;
const shardEnd = shardSize * (options.shardIndex + 1);

return [...tests]
.sort((a, b) => a.path.localeCompare(b.path))
.slice(shardStart, shardEnd);
}

return tests;
}

/**
* Sorting tests is very important because it has a great impact on the
* user-perceived responsiveness and speed of the test run.
Expand Down
7 changes: 7 additions & 0 deletions packages/jest-types/src/Config.ts
Expand Up @@ -279,6 +279,11 @@ type CoverageThreshold = {
global: CoverageThresholdValue;
};

type ShardConfig = {
shardIndex: number;
shardCount: number;
};

export type GlobalConfig = {
bail: number;
changedSince?: string;
Expand Down Expand Up @@ -322,6 +327,7 @@ export type GlobalConfig = {
reporters?: Array<string | ReporterConfig>;
runTestsByPath: boolean;
rootDir: string;
shard?: ShardConfig;
silent?: boolean;
skipFilter: boolean;
snapshotFormat: SnapshotFormat;
Expand Down Expand Up @@ -464,6 +470,7 @@ export type Argv = Arguments<
selectProjects: Array<string>;
setupFiles: Array<string>;
setupFilesAfterEnv: Array<string>;
shard: string;
showConfig: boolean;
silent: boolean;
snapshotSerializers: Array<string>;
Expand Down

0 comments on commit 6641e73

Please sign in to comment.