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

Exposes globalConfig to custom test sequencers #14535

Merged
merged 11 commits into from
Sep 19, 2023
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Fixes

- `[jest-leak-detector]` Make leak-detector more aggressive when running GC ([#14526](https://github.com/jestjs/jest/pull/14526))
- `[jest-test-sequencer, jest-core]` Exposes globalConfig & contexts to TestSequencer ([#14535](https://github.com/jestjs/jest/pull/14535))
SimenB marked this conversation as resolved.
Show resolved Hide resolved

### Performance

Expand Down
53 changes: 53 additions & 0 deletions e2e/__tests__/customTestSequencers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,56 @@ test('run failed tests async', () => {
.split('\n');
expect(sequence).toEqual(['./c.test.js', './d.test.js']);
});

test('run tests based on even seed', () => {
const result = runJest(
dir,
[
'-i',
'--config',
JSON.stringify({
testSequencer: '<rootDir>/testSequencerWithSeed.js',
}),
'--seed=2',
],
{},
);
console.log({result});
expect(result.exitCode).toBe(0);
const sequence = extractSummary(result.stderr)
.rest.replace(/PASS /g, '')
.split('\n');
expect(sequence).toEqual([
'./a.test.js',
'./b.test.js',
'./c.test.js',
'./d.test.js',
'./e.test.js',
]);
});

test('run tests based on odd seed', () => {
const result = runJest(
dir,
[
'-i',
'--config',
JSON.stringify({
testSequencer: '<rootDir>/testSequencerWithSeed.js',
}),
'--seed=1',
],
{},
);
expect(result.exitCode).toBe(0);
const sequence = extractSummary(result.stderr)
.rest.replace(/PASS /g, '')
.split('\n');
expect(sequence).toEqual([
'./e.test.js',
'./d.test.js',
'./c.test.js',
'./b.test.js',
'./a.test.js',
]);
});
26 changes: 26 additions & 0 deletions e2e/custom-test-sequencer/testSequencerWithSeed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

const Sequencer = require('@jest/test-sequencer').default;

class CustomSequencer extends Sequencer {
sort(tests) {
const copyTests = Array.from(tests);
const seed = this.globalConfig.seed;
const sortedTests = copyTests.sort((testA, testB) =>
testA.path > testB.path ? 1 : -1,
);

if (seed % 2 === 0) {
return sortedTests;
} else {
return sortedTests.reverse();
}
}
}

module.exports = CustomSequencer;
2 changes: 1 addition & 1 deletion packages/jest-core/src/runJest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export default async function runJest({
const Sequencer: typeof TestSequencer = await requireOrImportModule(
globalConfig.testSequencer,
);
const sequencer = new Sequencer();
const sequencer = new Sequencer({contexts, globalConfig});
let allTests: Array<Test> = [];

if (changedFilesPromise && globalConfig.watch) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as path from 'path';
import * as mockedFs from 'graceful-fs';
import type {AggregatedResult, Test, TestContext} from '@jest/test-result';
import {makeProjectConfig} from '@jest/test-utils';
import type {Config} from '@jest/types';
import TestSequencer from '../index';

jest.mock('graceful-fs', () => ({
Expand Down Expand Up @@ -56,7 +57,10 @@ const toTests = (paths: Array<string>) =>

beforeEach(() => {
jest.clearAllMocks();
sequencer = new TestSequencer();
sequencer = new TestSequencer({
contexts: [],
globalConfig: {} as Config.GlobalConfig,
});
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure if this is ideal, but since the Jest TestSequencer doesn't use either contexts, or globalConfig, it should be fine to have stand ins here, for now.

Alternatively we could make TestSequencerOptions optional, or even properly create contexts and globalConfig in the beforeEach.

lmk what you think.

Copy link
Member

Choose a reason for hiding this comment

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

in this test you can use

export const makeGlobalConfig = (

But it might need to be optional so it's not a breaking change (i.e. a newer version of @jest/test-scheduler than the version of @jest/core). However, I think the next release of Jest will be a major, in which case we don't need it to be optional.

});

test('sorts by file size if there is no timing information', () => {
Expand Down
14 changes: 14 additions & 0 deletions packages/jest-test-sequencer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,17 @@ import * as path from 'path';
import * as fs from 'graceful-fs';
import slash = require('slash');
import type {AggregatedResult, Test, TestContext} from '@jest/test-result';
import type {Config} from '@jest/types';
import HasteMap from 'jest-haste-map';

const FAIL = 0;
const SUCCESS = 1;

export type TestSequencerOptions = {
contexts: Array<TestContext>;
SimenB marked this conversation as resolved.
Show resolved Hide resolved
globalConfig: Config.GlobalConfig;
};

type Cache = {
[key: string]:
| [testStatus: typeof FAIL | typeof SUCCESS, testDuration: number]
Expand Down Expand Up @@ -46,6 +52,14 @@ type ShardPositionOptions = ShardOptions & {
export default class TestSequencer {
private readonly _cache = new Map<TestContext, Cache>();

protected readonly globalConfig: Config.GlobalConfig;
protected readonly contexts: Array<TestContext>;

constructor({contexts, globalConfig}: TestSequencerOptions) {
this.globalConfig = globalConfig;
this.contexts = contexts;
}
SimenB marked this conversation as resolved.
Show resolved Hide resolved

_getCachePath(testContext: TestContext): string {
const {config} = testContext;
const HasteMapClass = HasteMap.getStatic(config);
Expand Down