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

feat: add a seed value to test runs #13400

Merged
merged 57 commits into from Oct 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
886a1e2
planned documentation
Joshua-Hwang Oct 6, 2022
d551da5
expose seed to runtime
Joshua-Hwang Oct 6, 2022
4e44bef
WIP documentation
Joshua-Hwang Oct 6, 2022
0877be4
integration tests for passing the seed to runtime
Joshua-Hwang Oct 6, 2022
d1f5311
seed is generated when not supplied
Joshua-Hwang Oct 6, 2022
49c060f
fixed other tests
Joshua-Hwang Oct 6, 2022
bc8b4eb
improved documentation
Joshua-Hwang Oct 6, 2022
c5ca4d7
Merge branch 'facebook:main' into feat-get-seed
Joshua-Hwang Oct 6, 2022
fecf106
added copyright
Joshua-Hwang Oct 6, 2022
c39e63f
added replace seed function
Joshua-Hwang Oct 7, 2022
923f556
added Seed to reporter
Joshua-Hwang Oct 7, 2022
07f7b55
fixed some e2e tests for new Seed report
Joshua-Hwang Oct 7, 2022
3987577
updated line number of e2e test
Joshua-Hwang Oct 7, 2022
dda334e
updated snapshots
Joshua-Hwang Oct 7, 2022
3c61bc2
Merge branch 'main' of https://github.com/facebook/jest into feat-get…
jhwang98 Oct 7, 2022
08e53ac
added changelog
jhwang98 Oct 7, 2022
e848382
fixed e2e tests
jhwang98 Oct 7, 2022
9424589
lint fix
Joshua-Hwang Oct 7, 2022
83f5e9a
updated further snapshots
Joshua-Hwang Oct 7, 2022
bfcc0b4
removed seed from initial options
Joshua-Hwang Oct 8, 2022
3e78642
added typing tests
Joshua-Hwang Oct 8, 2022
ba7f269
documentation and messages made more clear
Joshua-Hwang Oct 8, 2022
40047fe
added --showSeed arg
Joshua-Hwang Oct 8, 2022
6730725
moved seed from environment to ctor
Joshua-Hwang Oct 8, 2022
589d434
snapshots updated
Joshua-Hwang Oct 8, 2022
4eafd79
fixed up e2e tests
Joshua-Hwang Oct 8, 2022
ebdf7cb
added new e2e test for --showSeed
Joshua-Hwang Oct 8, 2022
116bdca
linting fix
Joshua-Hwang Oct 8, 2022
b7d5faa
Merge branch 'feat-get-seed' of github.com:jhwang98/jest into feat-ge…
Joshua-Hwang Oct 8, 2022
c7d196a
added showseed docs
Joshua-Hwang Oct 8, 2022
d2af3a6
showSeed is the only way to print the seed
Joshua-Hwang Oct 8, 2022
2ea7dc6
e2e tests related are updated
Joshua-Hwang Oct 8, 2022
b9b8abf
snapshots updated
Joshua-Hwang Oct 8, 2022
679521b
added showSeed to config
Joshua-Hwang Oct 8, 2022
adda4f7
added e2e test for showSeed config
Joshua-Hwang Oct 8, 2022
01ac6f3
documented showSeed config
Joshua-Hwang Oct 8, 2022
71c04c4
updated docs
Joshua-Hwang Oct 8, 2022
1fee0a7
globalConfig passed instead and error thrown when seed is not present
Joshua-Hwang Oct 8, 2022
7b97aab
lint fix
Joshua-Hwang Oct 8, 2022
076eb02
replace Date.now
Joshua-Hwang Oct 8, 2022
d0eed4a
added showSeed test
Joshua-Hwang Oct 9, 2022
523b995
use jest.useFakeTimer instead
Joshua-Hwang Oct 9, 2022
1328b7b
removed optional chaining
Joshua-Hwang Oct 9, 2022
1aae653
lint fix and error now has message
Joshua-Hwang Oct 9, 2022
d568629
clarified docs
Joshua-Hwang Oct 9, 2022
e246b8a
Changelog updated
Joshua-Hwang Oct 9, 2022
3efe6fc
tweak changelog
SimenB Oct 9, 2022
0fb3e79
tweak link
SimenB Oct 9, 2022
d023a21
run same integration test
SimenB Oct 9, 2022
535730b
do not use snapshots in tests
SimenB Oct 9, 2022
3cb272b
tweak doc
SimenB Oct 9, 2022
b8a70eb
test bounds of seed
SimenB Oct 9, 2022
b2021e5
no hooks
SimenB Oct 9, 2022
91212a2
remove unused optional
SimenB Oct 9, 2022
ed5f9c1
oops
SimenB Oct 9, 2022
157ea7e
Merge branch 'main' into feat-get-seed
SimenB Oct 9, 2022
cf0230c
prettier
SimenB Oct 9, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,8 +2,11 @@

### Features

- `[@jest/cli, jest-config]` A seed for the test run will be randomly generated, or set by a CLI option ([#13400](https://github.com/facebook/jest/pull/13400))
- `[@jest/cli, jest-config]` `--show-seed` will display the seed value in the report, and can be set via a CLI flag or through the config file ([#13400](https://github.com/facebook/jest/pull/13400))
- `[jest-config]` Add `readInitialConfig` utility function ([#13356](https://github.com/facebook/jest/pull/13356))
- `[jest-core]` Enable testResultsProcessor to be async ([#13343](https://github.com/facebook/jest/pull/13343))
- `[@jest/environment, jest-environment-node, jest-environment-jsdom, jest-runtime]` Add `getSeed()` to the `jest` object ([#13400](https://github.com/facebook/jest/pull/13400))
- `[expect, @jest/expect-utils]` Allow `isA` utility to take a type argument ([#13355](https://github.com/facebook/jest/pull/13355))
- `[expect]` Expose `AsyncExpectationResult` and `SyncExpectationResult` types ([#13411](https://github.com/facebook/jest/pull/13411))

Expand Down
20 changes: 20 additions & 0 deletions docs/CLI.md
Expand Up @@ -350,6 +350,20 @@ The default regex matching works fine on small runs, but becomes slow if provide

:::

### `--seed=<num>`

Sets a seed value that can be retrieved in a test file via [`jest.getSeed()`](JestObjectAPI.md#jestgetseed). The seed value must be between `-0x80000000` and `0x7fffffff` inclusive (`-2147483648` (`-(2 ** 31)`) and `2147483647` (`2 ** 31 - 1`) in decimal).

```bash
jest --seed=1324
```

:::tip

If this option is not specified Jest will randomly generate the value. You can use the [`--showSeed`](#--showseed) flag to print the seed in the test report summary.

:::

### `--selectProjects <project1> ... <projectN>`

Run the tests of the specified projects. Jest uses the attribute `displayName` in the configuration to identify each project. If you use this option, you should provide a `displayName` to all your projects.
Expand Down Expand Up @@ -380,6 +394,12 @@ jest --shard=3/3

Print your Jest config and then exits.

### `--showSeed`
SimenB marked this conversation as resolved.
Show resolved Hide resolved

Prints the seed value in the test report summary. See [`--seed=<num>`](#--seednum) for the details.

Can also be set in configuration. See [`showSeed`](Configuration.md#showseed-boolean).

### `--silent`

Prevent tests from printing messages through the console.
Expand Down
6 changes: 6 additions & 0 deletions docs/Configuration.md
Expand Up @@ -1614,6 +1614,12 @@ const config: Config = {
export default config;
```

### `showSeed` \[boolean]

Default: `false`
Copy link
Contributor

Choose a reason for hiding this comment

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

Such a great option, I love it 💕

Copy link
Member

@SimenB SimenB Oct 9, 2022

Choose a reason for hiding this comment

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

I was just about to tag you 🙂 Does this (i.e. this entire implementation, not just this specific option) fit your use case and usage?

Copy link
Member

@SimenB SimenB Oct 9, 2022

Choose a reason for hiding this comment

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

I'm thinking you can just document setting this config flag instead of printing the seed yourself (as a major release I guess)

Copy link
Contributor

@dubzzz dubzzz Oct 9, 2022

Choose a reason for hiding this comment

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

It will definitely help a lot. So far @fast-check/jest was mostly responsible for building a seed for fast-check and appending it to the name of the it (and also provide a it tailored for pbt). With both seed and showSeed options altering the name of the test might not be that required.

Copy link
Contributor

Choose a reason for hiding this comment

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

I haven't documented the removal of the seed printing yet, but that was my idea when I opened dubzzz/fast-check#3287


The equivalent of the [`--showSeed`](CLI.md#--showseed) flag to print the seed in the test report summary.

### `slowTestThreshold` \[number]

Default: `5`
Expand Down
10 changes: 10 additions & 0 deletions docs/JestObjectAPI.md
Expand Up @@ -908,6 +908,16 @@ This function is not available when using legacy fake timers implementation.

## Misc

### `jest.getSeed()`

Every time Jest runs a seed value is randomly generated which you could use in a pseudorandom number generator or anywhere else.

:::tip

Use the [`--showSeed`](CLI.md#--showseed) flag to print the seed in the test report summary. To manually set the value of the seed use [`--seed=<num>`](CLI.md#--seednum) CLI argument.

:::

### `jest.setTimeout(timeout)`

Set the default timeout interval (in milliseconds) for all tests and before/after hooks in the test file. This only affects the test file from which this function is called. The default timeout interval is 5 seconds if this method is not called.
Expand Down
7 changes: 5 additions & 2 deletions e2e/Utils.ts
Expand Up @@ -157,6 +157,9 @@ export const copyDir = (src: string, dest: string) => {
}
};

export const replaceSeed = (str: string) =>
str.replace(/Seed: {8}(-?\d+)/g, 'Seed: <<REPLACED>>');

export const replaceTime = (str: string) =>
str
.replace(/\d*\.?\d+ m?s\b/g, '<<REPLACED>>')
Expand Down Expand Up @@ -201,7 +204,7 @@ export const extractSummary = (stdout: string) => {
const match = stdout
.replace(/(?:\\[rn])+/g, '\n')
.match(
/Test Suites:.*\nTests.*\nSnapshots.*\nTime.*(\nRan all test suites)*.*\n*$/gm,
/(Seed:.*\n)?Test Suites:.*\nTests.*\nSnapshots.*\nTime.*(\nRan all test suites)*.*\n*$/gm,
);
if (!match) {
throw new Error(dedent`
Expand Down Expand Up @@ -254,7 +257,7 @@ export const extractSummaries = (
stdout: string,
): Array<{rest: string; summary: string}> => {
const regex =
/Test Suites:.*\nTests.*\nSnapshots.*\nTime.*(\nRan all test suites)*.*\n*$/gm;
/(Seed:.*\n)?Test Suites:.*\nTests.*\nSnapshots.*\nTime.*(\nRan all test suites)*.*\n*$/gm;

let match = regex.exec(stdout);
const matches: Array<RegExpExecArray> = [];
Expand Down
1 change: 1 addition & 0 deletions e2e/__tests__/__snapshots__/showConfig.test.ts.snap
Expand Up @@ -125,6 +125,7 @@ exports[`--showConfig outputs config info and exits 1`] = `
"projects": [],
"rootDir": "<<REPLACED_ROOT_DIR>>",
"runTestsByPath": false,
"seed": <<RANDOM_SEED>>,
"skipFilter": false,
"snapshotFormat": {
"escapeString": false,
Expand Down
26 changes: 26 additions & 0 deletions e2e/__tests__/jestObject.test.ts
@@ -0,0 +1,26 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* 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 runJest from '../runJest';

const dir = path.resolve(__dirname, '../jest-object');

test('passes with seed', () => {
const result = runJest(dir, ['get-seed.test.js', '--seed', '1234']);
expect(result.exitCode).toBe(0);
});

test('fails with wrong seed', () => {
const result = runJest(dir, ['get-seed.test.js', '--seed', '1111']);
expect(result.exitCode).toBe(1);
});

test('seed always exists', () => {
const result = runJest(dir, ['any-seed.test.js']);
expect(result.exitCode).toBe(0);
});
3 changes: 2 additions & 1 deletion e2e/__tests__/showConfig.test.ts
Expand Up @@ -37,7 +37,8 @@ test('--showConfig outputs config info and exits', () => {
.replace(/"version": "(.+)"/g, '"version": "[version]"')
.replace(/"maxWorkers": (\d+)/g, '"maxWorkers": "[maxWorkers]"')
.replace(/"\S*show-config-test/gm, '"<<REPLACED_ROOT_DIR>>')
.replace(/"\S*\/jest\/packages/gm, '"<<REPLACED_JEST_PACKAGES_DIR>>');
.replace(/"\S*\/jest\/packages/gm, '"<<REPLACED_JEST_PACKAGES_DIR>>')
.replace(/"seed": (-?\d+)/g, '"seed": <<RANDOM_SEED>>');

expect(stdout).toMatchSnapshot();
});
52 changes: 52 additions & 0 deletions e2e/__tests__/showSeed.test.ts
@@ -0,0 +1,52 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* 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 {extractSummary, replaceSeed} from '../Utils';
import runJest from '../runJest';

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']);

const {summary} = extractSummary(stderr);

expect(replaceSeed(summary)).toMatch(randomSeedValueRegExp);
});

test('if --showSeed 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', () => {
const {stderr} = runJest(dir, [
'--seed',
'1234',
'--config',
'different-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']);

const {summary} = extractSummary(stderr);

expect(summary).toMatch(seedValueRegExp);
});
10 changes: 10 additions & 0 deletions e2e/jest-object/__tests__/any-seed.test.js
@@ -0,0 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

test('ensure seed exists', () => {
expect(jest.getSeed()).toEqual(expect.any(Number));
});
10 changes: 10 additions & 0 deletions e2e/jest-object/__tests__/get-seed.test.js
@@ -0,0 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

test('getSeed', () => {
expect(jest.getSeed()).toBe(1234);
});
4 changes: 4 additions & 0 deletions e2e/jest-object/different-config.json
@@ -0,0 +1,4 @@
{
"displayName": "Config from different-config.json file",
"showSeed": true
}
5 changes: 5 additions & 0 deletions e2e/jest-object/package.json
@@ -0,0 +1,5 @@
{
"jest": {
"testEnvironment": "node"
}
}
10 changes: 10 additions & 0 deletions packages/jest-cli/src/args.ts
Expand Up @@ -510,6 +510,11 @@ export const options: {[key: string]: Options} = {
"Allows to use a custom runner instead of Jest's default test runner.",
type: 'string',
},
seed: {
description:
'Sets a seed value that can be retrieved in a tests file via `jest.getSeed()`. If this option is not specified Jest will randomly generate the value. The seed value must be between `-0x80000000` and `0x7fffffff` inclusive.',
type: 'number',
},
selectProjects: {
description:
'Run the tests of the specified projects. ' +
Expand Down Expand Up @@ -541,6 +546,11 @@ export const options: {[key: string]: Options} = {
description: 'Print your jest config and then exits.',
type: 'boolean',
},
showSeed: {
description:
'Prints the seed value in the test report summary. See `--seed` for how to set this value',
type: 'boolean',
},
silent: {
description: 'Prevent tests from printing messages through the console.',
type: 'boolean',
Expand Down
1 change: 1 addition & 0 deletions packages/jest-config/src/ValidConfig.ts
Expand Up @@ -137,6 +137,7 @@ const initialOptions: Config.InitialOptions = {
sandboxInjectedGlobals: [],
setupFiles: ['<rootDir>/setup.js'],
setupFilesAfterEnv: ['<rootDir>/testSetupFile.js'],
showSeed: false,
silent: true,
skipFilter: false,
skipNodeResolution: false,
Expand Down
67 changes: 59 additions & 8 deletions packages/jest-config/src/__tests__/normalize.test.ts
Expand Up @@ -114,14 +114,12 @@ it('keeps custom ids based on the rootDir', async () => {
});

it('minimal config is stable across runs', async () => {
const firstNormalization = await normalize(
{rootDir: '/root/path/foo'},
{} as Config.Argv,
);
const secondNormalization = await normalize(
{rootDir: '/root/path/foo'},
{} as Config.Argv,
);
const firstNormalization = await normalize({rootDir: '/root/path/foo'}, {
seed: 55555,
} as Config.Argv);
const secondNormalization = await normalize({rootDir: '/root/path/foo'}, {
seed: 55555,
} as Config.Argv);

expect(firstNormalization).toEqual(secondNormalization);
expect(JSON.stringify(firstNormalization)).toBe(
Expand Down Expand Up @@ -2113,3 +2111,56 @@ it('parses workerIdleMemoryLimit', async () => {

expect(options.workerIdleMemoryLimit).toBe(47185920);
});

describe('seed', () => {
SimenB marked this conversation as resolved.
Show resolved Hide resolved
it('generates seed when not specified', async () => {
const {options} = await normalize({rootDir: '/root/'}, {} as Config.Argv);
expect(options.seed).toEqual(expect.any(Number));
});

it('uses seed specified', async () => {
const {options} = await normalize({rootDir: '/root/'}, {
seed: 4321,
} as Config.Argv);
expect(options.seed).toBe(4321);
});

it('throws if seed is too large or too small', async () => {
await expect(
normalize({rootDir: '/root/'}, {
seed: 2 ** 33,
} as Config.Argv),
).rejects.toThrow(
'seed value must be between `-0x80000000` and `0x7fffffff` inclusive - 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',
);
});
});

describe('showSeed', () => {
test('showSeed is set when argv flag is set', async () => {
const {options} = await normalize({rootDir: '/root/'}, {
showSeed: true,
} as Config.Argv);
expect(options.showSeed).toBe(true);
});

test('showSeed is set when the config is set', async () => {
const {options} = await normalize(
{rootDir: '/root/', showSeed: true},
{} as Config.Argv,
);
expect(options.showSeed).toBe(true);
});

test('showSeed is false when neither is set', async () => {
const {options} = await normalize({rootDir: '/root/'}, {} as Config.Argv);
expect(options.showSeed).toBeFalsy();
});
});
2 changes: 2 additions & 0 deletions packages/jest-config/src/index.ts
Expand Up @@ -115,7 +115,9 @@ const groupOptions = (
reporters: options.reporters,
rootDir: options.rootDir,
runTestsByPath: options.runTestsByPath,
seed: options.seed,
shard: options.shard,
showSeed: options.showSeed,
silent: options.silent,
skipFilter: options.skipFilter,
snapshotFormat: options.snapshotFormat,
Expand Down
19 changes: 19 additions & 0 deletions packages/jest-config/src/normalize.ts
Expand Up @@ -918,6 +918,7 @@ export default async function normalize(
case 'runTestsByPath':
case 'sandboxInjectedGlobals':
case 'silent':
case 'showSeed':
case 'skipFilter':
case 'skipNodeResolution':
case 'slowTestThreshold':
Expand Down Expand Up @@ -1021,6 +1022,24 @@ export default async function normalize(
newOptions.onlyChanged = newOptions.watch;
}

newOptions.showSeed = newOptions.showSeed || argv.showSeed;

const upperBoundSeedValue = 2 ** 31;

// xoroshiro128plus is used in v8 and is used here (at time of writing)
newOptions.seed =
argv.seed ??
Math.floor((2 ** 32 - 1) * Math.random() - upperBoundSeedValue);
if (
newOptions.seed < -upperBoundSeedValue ||
newOptions.seed > upperBoundSeedValue - 1
) {
throw new ValidationError(
'Validation Error',
`seed value must be between \`-0x80000000\` and \`0x7fffffff\` inclusive - is ${newOptions.seed}`,
);
}

if (!newOptions.onlyChanged) {
newOptions.onlyChanged = false;
}
Expand Down
Expand Up @@ -95,6 +95,7 @@ exports[`prints the config object 1`] = `
"reporters": [],
"rootDir": "/test_root_dir/",
"runTestsByPath": false,
"seed": 1234,
"silent": false,
"skipFilter": false,
"snapshotFormat": {},
Expand Down