mirror of https://github.com/facebook/jest.git
jest-circus runs children in shuffled order (#12922)
This commit is contained in:
parent
ab510f5c9e
commit
cbe0ac1b76
|
@ -3,6 +3,7 @@
|
|||
### Features
|
||||
|
||||
- `[jest-changed-files]` Support Sapling ([#13941](https://github.com/facebook/jest/pull/13941))
|
||||
- `[jest-circus, @jest/cli, jest-config]` Add feature to randomize order of tests via CLI flag or through the config file([#12922](https://github.com/facebook/jest/pull/12922))
|
||||
- `[jest-cli, jest-config, @jest/core, jest-haste-map, @jest/reporters, jest-runner, jest-runtime, @jest/types]` Add `workerThreads` configuration option to allow using [worker threads](https://nodejs.org/dist/latest/docs/api/worker_threads.html) for parallelization ([#13939](https://github.com/facebook/jest/pull/13939))
|
||||
- `[jest-config]` Add `openHandlesTimeout` option to configure possible open handles warning. ([#13875](https://github.com/facebook/jest/pull/13875))
|
||||
- `[@jest/create-cache-key-function]` Allow passing `length` argument to `createCacheKey()` function and set its default value to `16` on Windows ([#13827](https://github.com/facebook/jest/pull/13827))
|
||||
|
|
16
docs/CLI.md
16
docs/CLI.md
|
@ -332,6 +332,22 @@ If configuration files are found in the specified paths, _all_ projects specifie
|
|||
|
||||
:::
|
||||
|
||||
### `--randomize`
|
||||
|
||||
Shuffle the order of the tests within a file. The shuffling is based on the seed. See [`--seed=<num>`](#--seednum) for more info.
|
||||
|
||||
Seed value is displayed when this option is set. Equivalent to setting the CLI option [`--showSeed`](#--showseed).
|
||||
|
||||
```bash
|
||||
jest --randomize --seed 1234
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
This option is only supported using the default `jest-circus` test runner.
|
||||
|
||||
:::
|
||||
|
||||
### `--reporters`
|
||||
|
||||
Run tests with specified reporters. [Reporter options](configuration#reporters-arraymodulename--modulename-options) are not available via CLI. Example with multiple reporters:
|
||||
|
|
|
@ -1222,6 +1222,12 @@ With the `projects` option enabled, Jest will copy the root-level configuration
|
|||
|
||||
:::
|
||||
|
||||
### `randomize` \[boolean]
|
||||
|
||||
Default: `false`
|
||||
|
||||
The equivalent of the [`--randomize`](CLI.md#--randomize) flag to randomize the order of the tests in a file.
|
||||
|
||||
### `reporters` \[array<moduleName | \[moduleName, options]>]
|
||||
|
||||
Default: `undefined`
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`works with each 1`] = `
|
||||
" ✓ test1
|
||||
✓ test2
|
||||
✓ test3
|
||||
describe2
|
||||
✓ test4
|
||||
✓ test6
|
||||
✓ test5
|
||||
describe1
|
||||
✓ test4
|
||||
✓ test6
|
||||
✓ test5
|
||||
describe3
|
||||
✓ test11
|
||||
✓ test12
|
||||
✓ test10
|
||||
describe4
|
||||
✓ test14
|
||||
✓ test15
|
||||
✓ test13"
|
||||
`;
|
||||
|
||||
exports[`works with hooks 1`] = `
|
||||
" ✓ test1
|
||||
✓ test2
|
||||
✓ test3
|
||||
describe2
|
||||
✓ test7
|
||||
✓ test9
|
||||
✓ test8
|
||||
describe1
|
||||
✓ test4
|
||||
✓ test6
|
||||
✓ test5
|
||||
describe3
|
||||
✓ test11
|
||||
✓ test12
|
||||
✓ test10
|
||||
describe4
|
||||
✓ test14
|
||||
✓ test15
|
||||
✓ test13"
|
||||
`;
|
||||
|
||||
exports[`works with passing tests 1`] = `
|
||||
" ✓ test1
|
||||
✓ test2
|
||||
✓ test3
|
||||
describe2
|
||||
✓ test7
|
||||
✓ test9
|
||||
✓ test8
|
||||
describe1
|
||||
✓ test4
|
||||
✓ test6
|
||||
✓ test5
|
||||
describe3
|
||||
✓ test11
|
||||
✓ test12
|
||||
✓ test10
|
||||
describe4
|
||||
✓ test14
|
||||
✓ test15
|
||||
✓ test13"
|
||||
`;
|
||||
|
||||
exports[`works with snapshots 1`] = `
|
||||
" ✓ test1
|
||||
✓ test2
|
||||
✓ test3
|
||||
describe2
|
||||
✓ test4
|
||||
✓ test6
|
||||
✓ test5
|
||||
describe1
|
||||
✓ test4
|
||||
✓ test6
|
||||
✓ test5
|
||||
describe3
|
||||
✓ test11
|
||||
✓ test12
|
||||
✓ test10
|
||||
describe4
|
||||
✓ test14
|
||||
✓ test15
|
||||
✓ test13"
|
||||
`;
|
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import * as path from 'path';
|
||||
import {skipSuiteOnJasmine} from '@jest/test-utils';
|
||||
import {extractSummary} from '../Utils';
|
||||
import runJest, {RunJestResult} from '../runJest';
|
||||
|
||||
skipSuiteOnJasmine();
|
||||
|
||||
const dir = path.resolve(__dirname, '../randomize');
|
||||
|
||||
const trimFirstLine = (str: string): string =>
|
||||
str.split('\n').slice(1).join('\n');
|
||||
|
||||
function runJestTwice(
|
||||
dir: string,
|
||||
args: Array<string>,
|
||||
): [RunJestResult, RunJestResult] {
|
||||
return [
|
||||
runJest(dir, [...args, '--randomize']),
|
||||
runJest(dir, [...args, '--config', 'different-config.json']),
|
||||
];
|
||||
}
|
||||
|
||||
test('works with passing tests', () => {
|
||||
const [result1, result2] = runJestTwice(dir, [
|
||||
'success.test.js',
|
||||
'--seed',
|
||||
'123',
|
||||
]);
|
||||
|
||||
const rest1 = trimFirstLine(extractSummary(result1.stderr).rest);
|
||||
const rest2 = trimFirstLine(extractSummary(result2.stderr).rest);
|
||||
|
||||
expect(rest1).toEqual(rest2);
|
||||
expect(rest1).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('works with each', () => {
|
||||
const [result1, result2] = runJestTwice(dir, [
|
||||
'each.test.js',
|
||||
'--seed',
|
||||
'123',
|
||||
]);
|
||||
|
||||
const rest1 = trimFirstLine(extractSummary(result1.stderr).rest);
|
||||
const rest2 = trimFirstLine(extractSummary(result2.stderr).rest);
|
||||
|
||||
expect(rest1).toEqual(rest2);
|
||||
expect(rest1).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('works with hooks', () => {
|
||||
const [result1, result2] = runJestTwice(dir, [
|
||||
'hooks.test.js',
|
||||
'--seed',
|
||||
'123',
|
||||
]);
|
||||
|
||||
// Change in formatting could change this one
|
||||
const rest1 = trimFirstLine(extractSummary(result1.stderr).rest);
|
||||
const rest2 = trimFirstLine(extractSummary(result2.stderr).rest);
|
||||
|
||||
expect(rest1).toEqual(rest2);
|
||||
expect(rest1).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('works with snapshots', () => {
|
||||
const [result1, result2] = runJestTwice(dir, [
|
||||
'snapshots.test.js',
|
||||
'--seed',
|
||||
'123',
|
||||
]);
|
||||
|
||||
const rest1 = trimFirstLine(extractSummary(result1.stderr).rest);
|
||||
const rest2 = trimFirstLine(extractSummary(result2.stderr).rest);
|
||||
|
||||
expect(rest1).toEqual(rest2);
|
||||
expect(rest1).toMatchSnapshot();
|
||||
});
|
|
@ -14,39 +14,41 @@ const dir = path.resolve(__dirname, '../jest-object');
|
|||
const randomSeedValueRegExp = /Seed:\s+<<REPLACED>>/;
|
||||
const seedValueRegExp = /Seed:\s+1234/;
|
||||
|
||||
test('--showSeed changes report to output seed', () => {
|
||||
const {stderr} = runJest(dir, ['--showSeed', '--no-cache']);
|
||||
describe.each(['showSeed', 'randomize'])('Option %s', option => {
|
||||
test(`--${option} changes report to output seed`, () => {
|
||||
const {stderr} = runJest(dir, [`--${option}`, '--no-cache']);
|
||||
|
||||
const {summary} = extractSummary(stderr);
|
||||
|
||||
expect(replaceSeed(summary)).toMatch(randomSeedValueRegExp);
|
||||
});
|
||||
});
|
||||
|
||||
test('if --showSeed is not present the report will not show the seed', () => {
|
||||
test(`if --${option} is not present the report will not show the seed`, () => {
|
||||
const {stderr} = runJest(dir, ['--seed', '1234']);
|
||||
|
||||
const {summary} = extractSummary(stderr);
|
||||
|
||||
expect(replaceSeed(summary)).not.toMatch(randomSeedValueRegExp);
|
||||
});
|
||||
});
|
||||
|
||||
test('if showSeed is present in the config the report will show the seed', () => {
|
||||
test(`if ${option} is present in the config the report will show the seed`, () => {
|
||||
const {stderr} = runJest(dir, [
|
||||
'--seed',
|
||||
'1234',
|
||||
'--config',
|
||||
'different-config.json',
|
||||
`${option}-config.json`,
|
||||
]);
|
||||
|
||||
const {summary} = extractSummary(stderr);
|
||||
|
||||
expect(summary).toMatch(seedValueRegExp);
|
||||
});
|
||||
});
|
||||
|
||||
test('--seed --showSeed will show the seed in the report', () => {
|
||||
const {stderr} = runJest(dir, ['--showSeed', '--seed', '1234']);
|
||||
test(`--seed --${option} will show the seed in the report`, () => {
|
||||
const {stderr} = runJest(dir, [`--${option}`, '--seed', '1234']);
|
||||
|
||||
const {summary} = extractSummary(stderr);
|
||||
|
||||
expect(summary).toMatch(seedValueRegExp);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"displayName": "Config from randomize-config.json file",
|
||||
"randomize": true
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"displayName": "Config from showSeed-config.json file",
|
||||
"showSeed": true
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`describe1 test4 1`] = `4`;
|
||||
|
||||
exports[`describe1 test5 1`] = `5`;
|
||||
|
||||
exports[`describe1 test6 1`] = `6`;
|
||||
|
||||
exports[`describe2 test4 1`] = `4`;
|
||||
|
||||
exports[`describe2 test5 1`] = `5`;
|
||||
|
||||
exports[`describe2 test6 1`] = `6`;
|
||||
|
||||
exports[`describe3 describe4 test13 1`] = `13`;
|
||||
|
||||
exports[`describe3 describe4 test14 1`] = `14`;
|
||||
|
||||
exports[`describe3 describe4 test15 1`] = `15`;
|
||||
|
||||
exports[`describe3 test10 1`] = `10`;
|
||||
|
||||
exports[`describe3 test11 1`] = `11`;
|
||||
|
||||
exports[`describe3 test12 1`] = `12`;
|
||||
|
||||
exports[`test1 1`] = `1`;
|
||||
|
||||
exports[`test2 1`] = `2`;
|
||||
|
||||
exports[`test3 1`] = `3`;
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
it.each([1, 2, 3])('test%d', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
describe.each([1, 2])('describe%d', () => {
|
||||
it.each([4, 5, 6])('test%d', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('describe3', () => {
|
||||
it.each([10, 11, 12])('test%d', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
describe('describe4', () => {
|
||||
it.each([13, 14, 15])('test%d', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
beforeAll(() => {
|
||||
process.stdout.write('This is before all\n');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
process.stdout.write('This is before each\n');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.stdout.write('This is after each\n');
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
process.stdout.write('This is after all\n');
|
||||
});
|
||||
|
||||
it('test1', () => {
|
||||
process.stdout.write('test1\n');
|
||||
});
|
||||
|
||||
it('test2', () => {
|
||||
process.stdout.write('test2\n');
|
||||
});
|
||||
|
||||
it('test3', () => {
|
||||
process.stdout.write('test3\n');
|
||||
});
|
||||
|
||||
describe('describe1', () => {
|
||||
it('test4', () => {
|
||||
process.stdout.write('test4\n');
|
||||
});
|
||||
|
||||
it('test5', () => {
|
||||
process.stdout.write('test5\n');
|
||||
});
|
||||
|
||||
it('test6', () => {
|
||||
process.stdout.write('test6\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe('describe2', () => {
|
||||
afterAll(() => {
|
||||
process.stdout.write('This is after all describe2\n');
|
||||
});
|
||||
|
||||
it('test7', () => {
|
||||
process.stdout.write('test7\n');
|
||||
});
|
||||
|
||||
it('test8', () => {
|
||||
process.stdout.write('test8\n');
|
||||
});
|
||||
|
||||
it('test9', () => {
|
||||
process.stdout.write('test9\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe('describe3', () => {
|
||||
beforeEach(() => {
|
||||
process.stdout.write('This is before each describe3\n');
|
||||
});
|
||||
|
||||
it('test10', () => {
|
||||
process.stdout.write('test10\n');
|
||||
});
|
||||
|
||||
it('test11', () => {
|
||||
process.stdout.write('test11\n');
|
||||
});
|
||||
|
||||
it('test12', () => {
|
||||
process.stdout.write('test12\n');
|
||||
});
|
||||
|
||||
describe('describe4', () => {
|
||||
it('test13', () => {
|
||||
process.stdout.write('test13\n');
|
||||
});
|
||||
|
||||
it('test14', () => {
|
||||
process.stdout.write('test14\n');
|
||||
});
|
||||
|
||||
it('test15', () => {
|
||||
process.stdout.write('test15\n');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
it.each([1, 2, 3])('test%d', n => {
|
||||
expect(n).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe.each([1, 2])('describe%d', () => {
|
||||
it.each([4, 5, 6])('test%d', n => {
|
||||
expect(n).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('describe3', () => {
|
||||
it.each([10, 11, 12])('test%d', n => {
|
||||
expect(n).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('describe4', () => {
|
||||
it.each([13, 14, 15])('test%d', n => {
|
||||
expect(n).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
it('test1', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
it('test2', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
it('test3', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
describe('describe1', () => {
|
||||
it('test4', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
it('test5', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
it('test6', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('describe2', () => {
|
||||
it('test7', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
it('test8', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
it('test9', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('describe3', () => {
|
||||
it('test10', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
it('test11', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
it('test12', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
describe('describe4', () => {
|
||||
it('test13', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
it('test14', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
it('test15', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"displayName": "Config from different-config.json file",
|
||||
"showSeed": true
|
||||
"randomize": true
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"jest": {
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
|
@ -35,6 +35,7 @@
|
|||
"jest-util": "workspace:^",
|
||||
"p-limit": "^3.1.0",
|
||||
"pretty-format": "workspace:^",
|
||||
"pure-rand": "^6.0.0",
|
||||
"slash": "^3.0.0",
|
||||
"stack-utils": "^2.0.3"
|
||||
},
|
||||
|
|
|
@ -26,7 +26,10 @@ interface Result extends ExecaSyncReturnValue {
|
|||
error: string;
|
||||
}
|
||||
|
||||
export const runTest = (source: string) => {
|
||||
export const runTest = (
|
||||
source: string,
|
||||
opts?: {seed?: number; randomize?: boolean},
|
||||
) => {
|
||||
const filename = createHash('sha1')
|
||||
.update(source)
|
||||
.digest('hex')
|
||||
|
@ -44,7 +47,9 @@ export const runTest = (source: string) => {
|
|||
global.afterAll = circus.afterAll;
|
||||
|
||||
const testEventHandler = require('${TEST_EVENT_HANDLER_PATH}').default;
|
||||
const addEventHandler = require('${CIRCUS_STATE_PATH}').addEventHandler;
|
||||
const {addEventHandler, getState} = require('${CIRCUS_STATE_PATH}');
|
||||
getState().randomize = ${opts?.randomize};
|
||||
getState().seed = ${opts?.seed ?? 0};
|
||||
addEventHandler(testEventHandler);
|
||||
|
||||
${source};
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`failures 1`] = `
|
||||
"start_describe_definition: describe
|
||||
add_hook: beforeEach
|
||||
add_hook: afterEach
|
||||
add_test: one
|
||||
add_test: two
|
||||
finish_describe_definition: describe
|
||||
run_start
|
||||
run_describe_start: ROOT_DESCRIBE_BLOCK
|
||||
run_describe_start: describe
|
||||
test_start: one
|
||||
hook_start: beforeEach
|
||||
hook_success: beforeEach
|
||||
test_fn_start: one
|
||||
test_fn_failure: one
|
||||
hook_start: afterEach
|
||||
hook_failure: afterEach
|
||||
test_done: one
|
||||
test_start: two
|
||||
hook_start: beforeEach
|
||||
hook_success: beforeEach
|
||||
test_fn_start: two
|
||||
test_fn_success: two
|
||||
hook_start: afterEach
|
||||
hook_failure: afterEach
|
||||
test_done: two
|
||||
run_describe_finish: describe
|
||||
run_describe_finish: ROOT_DESCRIBE_BLOCK
|
||||
run_finish
|
||||
|
||||
unhandledErrors: 0"
|
||||
`;
|
||||
|
||||
exports[`function descriptors 1`] = `
|
||||
"start_describe_definition: describer
|
||||
add_test: One
|
||||
finish_describe_definition: describer
|
||||
run_start
|
||||
run_describe_start: ROOT_DESCRIBE_BLOCK
|
||||
run_describe_start: describer
|
||||
test_start: One
|
||||
test_fn_start: One
|
||||
test_fn_success: One
|
||||
test_done: One
|
||||
run_describe_finish: describer
|
||||
run_describe_finish: ROOT_DESCRIBE_BLOCK
|
||||
run_finish
|
||||
|
||||
unhandledErrors: 0"
|
||||
`;
|
||||
|
||||
exports[`simple test 1`] = `
|
||||
"start_describe_definition: describe
|
||||
add_hook: beforeEach
|
||||
add_hook: afterEach
|
||||
add_test: one
|
||||
add_test: two
|
||||
finish_describe_definition: describe
|
||||
run_start
|
||||
run_describe_start: ROOT_DESCRIBE_BLOCK
|
||||
run_describe_start: describe
|
||||
test_start: one
|
||||
hook_start: beforeEach
|
||||
hook_success: beforeEach
|
||||
test_fn_start: one
|
||||
test_fn_success: one
|
||||
hook_start: afterEach
|
||||
hook_success: afterEach
|
||||
test_done: one
|
||||
test_start: two
|
||||
hook_start: beforeEach
|
||||
hook_success: beforeEach
|
||||
test_fn_start: two
|
||||
test_fn_success: two
|
||||
hook_start: afterEach
|
||||
hook_success: afterEach
|
||||
test_done: two
|
||||
run_describe_finish: describe
|
||||
run_describe_finish: ROOT_DESCRIBE_BLOCK
|
||||
run_finish
|
||||
|
||||
unhandledErrors: 0"
|
||||
`;
|
|
@ -0,0 +1,106 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`rngBuilder creates a randomizer given seed 1 1`] = `
|
||||
Array [
|
||||
0,
|
||||
2,
|
||||
4,
|
||||
0,
|
||||
2,
|
||||
8,
|
||||
5,
|
||||
9,
|
||||
9,
|
||||
5,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`rngBuilder creates a randomizer given seed 2 1`] = `
|
||||
Array [
|
||||
10,
|
||||
1,
|
||||
0,
|
||||
7,
|
||||
4,
|
||||
4,
|
||||
5,
|
||||
0,
|
||||
10,
|
||||
3,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`rngBuilder creates a randomizer given seed 4 1`] = `
|
||||
Array [
|
||||
8,
|
||||
10,
|
||||
3,
|
||||
2,
|
||||
5,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
8,
|
||||
5,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`rngBuilder creates a randomizer given seed 8 1`] = `
|
||||
Array [
|
||||
4,
|
||||
6,
|
||||
0,
|
||||
5,
|
||||
10,
|
||||
0,
|
||||
3,
|
||||
9,
|
||||
5,
|
||||
6,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`rngBuilder creates a randomizer given seed 16 1`] = `
|
||||
Array [
|
||||
7,
|
||||
9,
|
||||
3,
|
||||
2,
|
||||
8,
|
||||
1,
|
||||
6,
|
||||
1,
|
||||
10,
|
||||
1,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`shuffleArray shuffles list ["a", "b", "c", "d"] 1`] = `
|
||||
Array [
|
||||
"c",
|
||||
"b",
|
||||
"a",
|
||||
"d",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`shuffleArray shuffles list ["a", "b", "c"] 1`] = `
|
||||
Array [
|
||||
"b",
|
||||
"a",
|
||||
"c",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`shuffleArray shuffles list ["a", "b"] 1`] = `
|
||||
Array [
|
||||
"a",
|
||||
"b",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`shuffleArray shuffles list ["a"] 1`] = `
|
||||
Array [
|
||||
"a",
|
||||
]
|
||||
`;
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {runTest} from '../__mocks__/testUtils';
|
||||
|
||||
test('simple test', () => {
|
||||
const {stdout} = runTest(
|
||||
`
|
||||
describe('describe', () => {
|
||||
beforeEach(() => {});
|
||||
afterEach(() => {});
|
||||
test('one', () => {});
|
||||
test('two', () => {});
|
||||
})
|
||||
`,
|
||||
{randomize: true, seed: 3},
|
||||
);
|
||||
|
||||
expect(stdout).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('function descriptors', () => {
|
||||
const {stdout} = runTest(
|
||||
`
|
||||
describe(function describer() {}, () => {
|
||||
test(class One {}, () => {});
|
||||
})
|
||||
`,
|
||||
{randomize: true, seed: 3},
|
||||
);
|
||||
|
||||
expect(stdout).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('failures', () => {
|
||||
const {stdout} = runTest(
|
||||
`
|
||||
describe('describe', () => {
|
||||
beforeEach(() => {});
|
||||
afterEach(() => { throw new Error('banana')});
|
||||
test('one', () => { throw new Error('kentucky')});
|
||||
test('two', () => {});
|
||||
})
|
||||
`,
|
||||
{randomize: true, seed: 3},
|
||||
);
|
||||
|
||||
expect(stdout).toMatchSnapshot();
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import shuffleArray, {rngBuilder} from '../shuffleArray';
|
||||
|
||||
describe('rngBuilder', () => {
|
||||
// Breaking these orders would be a breaking change
|
||||
// Some people will be using seeds relying on a particular order
|
||||
test.each([1, 2, 4, 8, 16])('creates a randomizer given seed %s', seed => {
|
||||
const rng = rngBuilder(seed);
|
||||
const results = Array(10)
|
||||
.fill(0)
|
||||
.map(() => rng.next(0, 10));
|
||||
expect(results).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('shuffleArray', () => {
|
||||
test('empty array is shuffled', () => {
|
||||
const shuffled = shuffleArray([], rngBuilder(seed));
|
||||
expect(shuffled).toEqual([]);
|
||||
});
|
||||
|
||||
// Breaking these orders would be a breaking change
|
||||
// Some people will be using seeds relying on a particular order
|
||||
const seed = 321;
|
||||
test.each([[['a']], [['a', 'b']], [['a', 'b', 'c']], [['a', 'b', 'c', 'd']]])(
|
||||
'shuffles list %p',
|
||||
l => {
|
||||
expect(shuffleArray(l, rngBuilder(seed))).toMatchSnapshot();
|
||||
},
|
||||
);
|
||||
});
|
|
@ -63,6 +63,9 @@ export const initialize = async ({
|
|||
}
|
||||
getRunnerState().maxConcurrency = globalConfig.maxConcurrency;
|
||||
|
||||
getRunnerState().randomize = globalConfig.randomize;
|
||||
getRunnerState().seed = globalConfig.seed;
|
||||
|
||||
// @ts-expect-error: missing `concurrent` which is added later
|
||||
const globalsObject: Global.TestFrameworkGlobals = {
|
||||
...globals,
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import pLimit = require('p-limit');
|
||||
import type {Circus} from '@jest/types';
|
||||
import shuffleArray, {RandomNumberGenerator, rngBuilder} from './shuffleArray';
|
||||
import {dispatch, getState} from './state';
|
||||
import {RETRY_TIMES} from './types';
|
||||
import {
|
||||
|
@ -19,9 +20,10 @@ import {
|
|||
} from './utils';
|
||||
|
||||
const run = async (): Promise<Circus.RunResult> => {
|
||||
const {rootDescribeBlock} = getState();
|
||||
const {rootDescribeBlock, seed, randomize} = getState();
|
||||
const rng = randomize ? rngBuilder(seed) : undefined;
|
||||
await dispatch({name: 'run_start'});
|
||||
await _runTestsForDescribeBlock(rootDescribeBlock, true);
|
||||
await _runTestsForDescribeBlock(rootDescribeBlock, rng, true);
|
||||
await dispatch({name: 'run_finish'});
|
||||
return makeRunResult(
|
||||
getState().rootDescribeBlock,
|
||||
|
@ -31,6 +33,7 @@ const run = async (): Promise<Circus.RunResult> => {
|
|||
|
||||
const _runTestsForDescribeBlock = async (
|
||||
describeBlock: Circus.DescribeBlock,
|
||||
rng: RandomNumberGenerator | undefined,
|
||||
isRootBlock = false,
|
||||
) => {
|
||||
await dispatch({describeBlock, name: 'run_describe_start'});
|
||||
|
@ -68,10 +71,13 @@ const _runTestsForDescribeBlock = async (
|
|||
const retryTimes = parseInt(global[RETRY_TIMES], 10) || 0;
|
||||
const deferredRetryTests = [];
|
||||
|
||||
if (rng) {
|
||||
describeBlock.children = shuffleArray(describeBlock.children, rng);
|
||||
}
|
||||
for (const child of describeBlock.children) {
|
||||
switch (child.type) {
|
||||
case 'describeBlock': {
|
||||
await _runTestsForDescribeBlock(child);
|
||||
await _runTestsForDescribeBlock(child, rng);
|
||||
break;
|
||||
}
|
||||
case 'test': {
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {unsafeUniformIntDistribution, xoroshiro128plus} from 'pure-rand';
|
||||
|
||||
// Generates [from, to] inclusive
|
||||
export type RandomNumberGenerator = {
|
||||
next: (from: number, to: number) => number;
|
||||
};
|
||||
|
||||
export const rngBuilder = (seed: number): RandomNumberGenerator => {
|
||||
const gen = xoroshiro128plus(seed);
|
||||
return {next: (from, to) => unsafeUniformIntDistribution(from, to, gen)};
|
||||
};
|
||||
|
||||
// Fisher-Yates shuffle
|
||||
// This is performed in-place
|
||||
export default function shuffleArray<T>(
|
||||
array: Array<T>,
|
||||
random: RandomNumberGenerator,
|
||||
): Array<T> {
|
||||
const length = array.length;
|
||||
if (length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
const n = random.next(i, length - 1);
|
||||
const value = array[i];
|
||||
array[i] = array[n];
|
||||
array[n] = value;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
|
@ -30,6 +30,7 @@ const createState = (): Circus.State => {
|
|||
maxConcurrency: 5,
|
||||
parentProcess: null,
|
||||
rootDescribeBlock: ROOT_DESCRIBE_BLOCK,
|
||||
seed: 0,
|
||||
testNamePattern: null,
|
||||
testTimeout: 5000,
|
||||
unhandledErrors: [],
|
||||
|
|
|
@ -455,6 +455,11 @@ export const options: {[key: string]: Options} = {
|
|||
string: true,
|
||||
type: 'array',
|
||||
},
|
||||
randomize: {
|
||||
description:
|
||||
'Shuffle the order of the tests within a file. In order to choose the seed refer to the `--seed` CLI option.',
|
||||
type: 'boolean',
|
||||
},
|
||||
reporters: {
|
||||
description: 'A list of custom reporters for the test suite.',
|
||||
string: true,
|
||||
|
|
|
@ -121,6 +121,7 @@ export const initialOptions: Config.InitialOptions = {
|
|||
preset: 'react-native',
|
||||
prettierPath: '<rootDir>/node_modules/prettier',
|
||||
projects: ['project-a', 'project-b/'],
|
||||
randomize: false,
|
||||
reporters: [
|
||||
'default',
|
||||
'custom-reporter-1',
|
||||
|
|
|
@ -2150,14 +2150,14 @@ describe('seed', () => {
|
|||
seed: 2 ** 33,
|
||||
} as Config.Argv),
|
||||
).rejects.toThrow(
|
||||
'seed value must be between `-0x80000000` and `0x7fffffff` inclusive - is 8589934592',
|
||||
'seed value must be between `-0x80000000` and `0x7fffffff` inclusive - instead it is 8589934592',
|
||||
);
|
||||
await expect(
|
||||
normalize({rootDir: '/root/'}, {
|
||||
seed: -(2 ** 33),
|
||||
} as Config.Argv),
|
||||
).rejects.toThrow(
|
||||
'seed value must be between `-0x80000000` and `0x7fffffff` inclusive - is -8589934592',
|
||||
'seed value must be between `-0x80000000` and `0x7fffffff` inclusive - instead it is -8589934592',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -2182,4 +2182,34 @@ describe('showSeed', () => {
|
|||
const {options} = await normalize({rootDir: '/root/'}, {} as Config.Argv);
|
||||
expect(options.showSeed).toBeFalsy();
|
||||
});
|
||||
|
||||
test('showSeed is true when randomize is set', async () => {
|
||||
const {options} = await normalize(
|
||||
{randomize: true, rootDir: '/root/'},
|
||||
{} as Config.Argv,
|
||||
);
|
||||
expect(options.showSeed).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('randomize', () => {
|
||||
test('randomize is set when argv flag is set', async () => {
|
||||
const {options} = await normalize({rootDir: '/root/'}, {
|
||||
randomize: true,
|
||||
} as Config.Argv);
|
||||
expect(options.randomize).toBe(true);
|
||||
});
|
||||
|
||||
test('randomize is set when the config is set', async () => {
|
||||
const {options} = await normalize(
|
||||
{randomize: true, rootDir: '/root/'},
|
||||
{} as Config.Argv,
|
||||
);
|
||||
expect(options.randomize).toBe(true);
|
||||
});
|
||||
|
||||
test('randomize is false when neither is set', async () => {
|
||||
const {options} = await normalize({rootDir: '/root/'}, {} as Config.Argv);
|
||||
expect(options.randomize).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -117,6 +117,7 @@ const groupOptions = (
|
|||
outputFile: options.outputFile,
|
||||
passWithNoTests: options.passWithNoTests,
|
||||
projects: options.projects,
|
||||
randomize: options.randomize,
|
||||
replname: options.replname,
|
||||
reporters: options.reporters,
|
||||
rootDir: options.rootDir,
|
||||
|
|
|
@ -915,6 +915,7 @@ export default async function normalize(
|
|||
case 'openHandlesTimeout':
|
||||
case 'outputFile':
|
||||
case 'passWithNoTests':
|
||||
case 'randomize':
|
||||
case 'replname':
|
||||
case 'resetMocks':
|
||||
case 'resetModules':
|
||||
|
@ -1028,11 +1029,14 @@ export default async function normalize(
|
|||
newOptions.onlyChanged = newOptions.watch;
|
||||
}
|
||||
|
||||
newOptions.showSeed = newOptions.showSeed || argv.showSeed;
|
||||
newOptions.randomize = newOptions.randomize || argv.randomize;
|
||||
|
||||
newOptions.showSeed =
|
||||
newOptions.randomize || newOptions.showSeed || argv.showSeed;
|
||||
|
||||
const upperBoundSeedValue = 2 ** 31;
|
||||
|
||||
// xoroshiro128plus is used in v8 and is used here (at time of writing)
|
||||
// bounds are determined by xoroshiro128plus which is used in v8 and is used here (at time of writing)
|
||||
newOptions.seed =
|
||||
argv.seed ??
|
||||
Math.floor((2 ** 32 - 1) * Math.random() - upperBoundSeedValue);
|
||||
|
@ -1042,7 +1046,7 @@ export default async function normalize(
|
|||
) {
|
||||
throw new ValidationError(
|
||||
'Validation Error',
|
||||
`seed value must be between \`-0x80000000\` and \`0x7fffffff\` inclusive - is ${newOptions.seed}`,
|
||||
`seed value must be between \`-0x80000000\` and \`0x7fffffff\` inclusive - instead it is ${newOptions.seed}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -215,7 +215,9 @@ export type State = {
|
|||
// the original ones.
|
||||
originalGlobalErrorHandlers?: GlobalErrorHandlers;
|
||||
parentProcess: Process | null; // process object from the outer scope
|
||||
randomize?: boolean;
|
||||
rootDescribeBlock: DescribeBlock;
|
||||
seed: number;
|
||||
testNamePattern?: RegExp | null;
|
||||
testTimeout: number;
|
||||
unhandledErrors: Array<Exception>;
|
||||
|
|
|
@ -281,6 +281,7 @@ export type InitialOptions = Partial<{
|
|||
preset: string | null | undefined;
|
||||
prettierPath: string | null | undefined;
|
||||
projects: Array<string | InitialProjectOptions>;
|
||||
randomize: boolean;
|
||||
replname: string | null | undefined;
|
||||
resetMocks: boolean;
|
||||
resetModules: boolean;
|
||||
|
@ -396,6 +397,7 @@ export type GlobalConfig = {
|
|||
openHandlesTimeout: number;
|
||||
passWithNoTests: boolean;
|
||||
projects: Array<string>;
|
||||
randomize?: boolean;
|
||||
replname?: string;
|
||||
reporters?: Array<ReporterConfig>;
|
||||
runTestsByPath: boolean;
|
||||
|
@ -541,6 +543,7 @@ export type Argv = Arguments<
|
|||
preset: string | null | undefined;
|
||||
prettierPath: string | null | undefined;
|
||||
projects: Array<string>;
|
||||
randomize: boolean;
|
||||
reporters: Array<string>;
|
||||
resetMocks: boolean;
|
||||
resetModules: boolean;
|
||||
|
|
Loading…
Reference in New Issue