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

✨(jest) Take seed coming from Jest whenever provided #3332

Merged
merged 3 commits into from
Oct 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .yarn/versions/ce9dc069.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
releases:
"@fast-check/jest": minor
11 changes: 9 additions & 2 deletions packages/jest/src/jest-fast-check.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { it, test } from '@jest/globals';
import { it, test, jest } from '@jest/globals';
import * as fc from 'fast-check';

type It = typeof it;
Expand Down Expand Up @@ -29,7 +29,14 @@ function internalTestPropExecute<Ts extends [any] | any[]>(
if (seedFromGlobals !== undefined) {
customParams.seed = seedFromGlobals;
} else {
customParams.seed = Date.now() ^ (Math.random() * 0x100000000);
// This option is only available since v29.2.0 of Jest
// See official release note: https://github.com/facebook/jest/releases/tag/v29.2.0
const seedFromJest = typeof jest.getSeed === 'function' ? jest.getSeed() : undefined;
if (seedFromJest !== undefined) {
customParams.seed = seedFromJest;
} else {
customParams.seed = Date.now() ^ (Math.random() * 0x100000000);
}
}
}

Expand Down
55 changes: 47 additions & 8 deletions packages/jest/test/jest-fast-check.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ describe.each<{ runner: RunnerType }>([{ runner: 'testProp' }, { runner: 'itProp

// Assert
expectFail(out, specFileName);
expectAlignedSeeds(out);
expectAlignedSeeds(out, { noAlignWithJest: true });
expect(out).toMatch(/[×✕] property fail with locally requested seed \(with seed=4242\)/);
});

Expand All @@ -121,10 +121,25 @@ describe.each<{ runner: RunnerType }>([{ runner: 'testProp' }, { runner: 'itProp

// Assert
expectFail(out, specFileName);
expectAlignedSeeds(out);
expectAlignedSeeds(out, { noAlignWithJest: true });
expect(out).toMatch(/[×✕] property fail with globally requested seed \(with seed=4848\)/);
});

it.concurrent('should fail with seed requested at jest level', async () => {
// Arrange
const { specFileName, jestConfigRelativePath } = await writeToFile(runner, () => {
runnerProp('property fail with globally requested seed', [fc.constant(null)], (_unused) => false);
});

// Act
const out = await runSpec(jestConfigRelativePath, { jestSeed: 6969 });

// Assert
expectFail(out, specFileName);
expectAlignedSeeds(out);
expect(out).toMatch(/[×✕] property fail with globally requested seed \(with seed=6969\)/);
});

describe('.skip', () => {
it.concurrent('should never be executed', async () => {
// Arrange
Expand Down Expand Up @@ -271,11 +286,17 @@ async function writeToFile(
return { specFileName, jestConfigRelativePath };
}

async function runSpec(jestConfigRelativePath: string): Promise<string> {
async function runSpec(jestConfigRelativePath: string, opts: { jestSeed?: number } = {}): Promise<string> {
const { stdout: jestBinaryPathCommand } = await execFile('yarn', ['bin', 'jest'], { shell: true });
const jestBinaryPath = jestBinaryPathCommand.split('\n')[0];
try {
const { stderr: specOutput } = await execFile('node', [jestBinaryPath, '--config', jestConfigRelativePath]);
const { stderr: specOutput } = await execFile('node', [
jestBinaryPath,
'--config',
jestConfigRelativePath,
'--show-seed',
...(opts.jestSeed !== undefined ? ['--seed', String(opts.jestSeed)] : []),
]);
return specOutput;
} catch (err) {
return (err as any).stderr;
Expand All @@ -290,8 +311,26 @@ function expectFail(out: string, specFileName: string): void {
expect(out).toMatch(new RegExp('FAIL .*/' + specFileName));
}

function expectAlignedSeeds(out: string): void {
expect(out).toMatch(/[×✕] .* \(with seed=-?\d+\)/);
const receivedSeed = out.split('seed=')[1].split(')')[0];
expect(out).toMatch(new RegExp('seed\\s*:\\s*' + receivedSeed + '[^\\d]'));
function expectAlignedSeeds(out: string, opts: { noAlignWithJest?: boolean } = {}): void {
// Seed printed by jest has the shape:
// > Seed: -518086725
// > Test Suites: 1 failed, 1 total
// > Tests: 1 failed, 1 total
// > Snapshots: 0 total
// > Time: 0.952 s
// > Ran all test suites
const JestSeedMatcher = /Seed:\s+(-?\d+)/;
expect(out).toMatch(JestSeedMatcher);
const jestSeed = JestSeedMatcher.exec(out)![1];
// Seed printed by jest-fast-check next to test name has the shape:
// > × property fail on falsy property (with seed=-518086725)
const JestFastCheckSeedMatcher = opts.noAlignWithJest
? /[×✕] .* \(with seed=(-?\d+)\)/
: new RegExp('[×✕] .* \\(with seed=(' + jestSeed + ')\\)');
expect(out).toMatch(JestFastCheckSeedMatcher);
const jestFastCheckSeed = JestFastCheckSeedMatcher.exec(out)![1];
// Seed printed by fast-check in case of failure has the shape:
// > Property failed after 1 tests
// > { seed: -518086725, path: \"0\", endOnFailure: true }
expect(out).toMatch(new RegExp('\\{[^}]*seed\\s*:\\s*' + jestFastCheckSeed + '[^\\d]'));
}