diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d08af002275..906e1998ec31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Fixes +- `[@jest/test-sequencer]` Make sure sharding does not produce empty groups ([#13476](https://github.com/facebook/jest/pull/13476)) + ### Chore & Maintenance ### Performance diff --git a/packages/jest-test-sequencer/src/__tests__/test_sequencer.test.ts b/packages/jest-test-sequencer/src/__tests__/test_sequencer.test.ts index 88b5bb4b9c17..87a6b8157f91 100644 --- a/packages/jest-test-sequencer/src/__tests__/test_sequencer.test.ts +++ b/packages/jest-test-sequencer/src/__tests__/test_sequencer.test.ts @@ -368,6 +368,23 @@ test('returns expected 100/8 shards', async () => { ); expect(shards.map(shard => shard.length)).toEqual([ - 13, 13, 13, 13, 13, 13, 13, 9, + 13, 13, 13, 13, 12, 12, 12, 12, + ]); +}); + +test('returns expected 55/12 shards', async () => { + const allTests = toTests(new Array(55).fill(true).map((_, i) => `/${i}.js`)); + + const shards = await Promise.all( + new Array(12).fill(true).map((_, i) => + sequencer.shard(allTests, { + shardCount: 12, + shardIndex: i + 1, + }), + ), + ); + + expect(shards.map(shard => shard.length)).toEqual([ + 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, ]); }); diff --git a/packages/jest-test-sequencer/src/index.ts b/packages/jest-test-sequencer/src/index.ts index f55b5ea4c1f2..7369397a8b80 100644 --- a/packages/jest-test-sequencer/src/index.ts +++ b/packages/jest-test-sequencer/src/index.ts @@ -24,6 +24,10 @@ export type ShardOptions = { shardCount: number; }; +type ShardPositionOptions = ShardOptions & { + suiteLength: number; +}; + /** * The TestSequencer will ultimately decide which tests should run first. * It is responsible for storing and reading from a local cache @@ -72,6 +76,19 @@ export default class TestSequencer { return cache; } + private _shardPosition(options: ShardPositionOptions): number { + const shardRest = options.suiteLength % options.shardCount; + const ratio = options.suiteLength / options.shardCount; + + return new Array(options.shardIndex) + .fill(true) + .reduce((acc, _, shardIndex) => { + const dangles = shardIndex < shardRest; + const shardSize = dangles ? Math.ceil(ratio) : Math.floor(ratio); + return acc + shardSize; + }, 0); + } + /** * Select tests for shard requested via --shard=shardIndex/shardCount * Sharding is applied before sorting @@ -97,9 +114,17 @@ export default class TestSequencer { tests: Array, options: ShardOptions, ): Array | Promise> { - const shardSize = Math.ceil(tests.length / options.shardCount); - const shardStart = shardSize * (options.shardIndex - 1); - const shardEnd = shardSize * options.shardIndex; + const shardStart = this._shardPosition({ + shardCount: options.shardCount, + shardIndex: options.shardIndex - 1, + suiteLength: tests.length, + }); + + const shardEnd = this._shardPosition({ + shardCount: options.shardCount, + shardIndex: options.shardIndex, + suiteLength: tests.length, + }); return tests .map(test => {