Skip to content

Commit

Permalink
feat: add initial seed parameter to constructors (#3220)
Browse files Browse the repository at this point in the history
  • Loading branch information
ST-DDT authored Nov 12, 2024
1 parent cb4b77c commit 1633c8d
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 31 deletions.
16 changes: 15 additions & 1 deletion src/faker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ export class Faker extends SimpleFaker {
* Specify this only if you want to use it to achieve a specific goal,
* such as sharing the same random generator with other instances/tools.
* Defaults to faker's Mersenne Twister based pseudo random number generator.
* @param options.seed The initial seed to use.
* The seed can be used to generate reproducible values.
* Refer to the `seed()` method for more information.
* Defaults to a random seed.
*
* @example
* import { Faker, es } from '@faker-js/faker';
Expand Down Expand Up @@ -157,8 +161,18 @@ export class Faker extends SimpleFaker {
* @default generateMersenne53Randomizer()
*/
randomizer?: Randomizer;

/**
* The initial seed to use.
* The seed can be used to generate reproducible values.
*
* Refer to the `seed()` method for more information.
*
* Defaults to a random seed.
*/
seed?: number;
}) {
super({ randomizer: options.randomizer });
super({ randomizer: options.randomizer, seed: options.seed });

let { locale } = options;

Expand Down
8 changes: 8 additions & 0 deletions src/internal/seed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Generates a random seed.
*
* @internal
*/
export function randomSeed(): number {
return Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER);
}
27 changes: 22 additions & 5 deletions src/simple-faker.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { randomSeed } from './internal/seed';
import { DatatypeModule } from './modules/datatype';
import { SimpleDateModule } from './modules/date';
import { SimpleHelpersModule } from './modules/helpers';
Expand Down Expand Up @@ -97,6 +98,10 @@ export class SimpleFaker {
* Specify this only if you want to use it to achieve a specific goal,
* such as sharing the same random generator with other instances/tools.
* Defaults to faker's Mersenne Twister based pseudo random number generator.
* @param options.seed The initial seed to use.
* The seed can be used to generate reproducible values.
* Refer to the `seed()` method for more information.
* Defaults to a random seed.
*
* @example
* import { SimpleFaker } from '@faker-js/faker';
Expand All @@ -120,11 +125,25 @@ export class SimpleFaker {
* @default generateMersenne53Randomizer()
*/
randomizer?: Randomizer;

/**
* The initial seed to use.
* The seed can be used to generate reproducible values.
*
* Refer to the `seed()` method for more information.
*
* Defaults to a random seed.
*/
seed?: number;
} = {}
) {
const { randomizer = generateMersenne53Randomizer() } = options;
const { randomizer, seed } = options;

if (randomizer != null && seed != null) {
randomizer.seed(seed);
}

this._randomizer = randomizer;
this._randomizer = randomizer ?? generateMersenne53Randomizer(seed);
}

/**
Expand Down Expand Up @@ -247,9 +266,7 @@ export class SimpleFaker {
* @since 6.0.0
*/
seed(seed?: number | number[]): number | number[];
seed(
seed: number | number[] = Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER)
): number | number[] {
seed(seed: number | number[] = randomSeed()): number | number[] {
this._randomizer.seed(seed);

return seed;
Expand Down
17 changes: 13 additions & 4 deletions src/utils/mersenne.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { MersenneTwister19937 } from '../internal/mersenne';
import { randomSeed } from '../internal/seed';
import type { Randomizer } from '../randomizer';

/**
* Generates a MersenneTwister19937 randomizer with 32 bits of precision.
* This is the default randomizer used by faker prior to v9.0.
*
* @param seed The initial seed to use. Defaults to a random number.
*
* @example
* import { de, en, generateMersenne32Randomizer, Faker } from '@faker-js/faker';
*
Expand All @@ -16,10 +19,12 @@ import type { Randomizer } from '../randomizer';
*
* @since 8.2.0
*/
export function generateMersenne32Randomizer(): Randomizer {
export function generateMersenne32Randomizer(
seed: number = randomSeed()
): Randomizer {
const twister = new MersenneTwister19937();

twister.initGenrand(Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER));
twister.initGenrand(seed);

return {
next(): number {
Expand All @@ -39,6 +44,8 @@ export function generateMersenne32Randomizer(): Randomizer {
* Generates a MersenneTwister19937 randomizer with 53 bits of precision.
* This is the default randomizer used by faker starting with v9.0.
*
* @param seed The initial seed to use. Defaults to a random number.
*
* @example
* import { de, en, generateMersenne53Randomizer, Faker } from '@faker-js/faker';
*
Expand All @@ -50,10 +57,12 @@ export function generateMersenne32Randomizer(): Randomizer {
*
* @since 9.0.0
*/
export function generateMersenne53Randomizer(): Randomizer {
export function generateMersenne53Randomizer(
seed: number = randomSeed()
): Randomizer {
const twister = new MersenneTwister19937();

twister.initGenrand(Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER));
twister.initGenrand(seed);

return {
next(): number {
Expand Down
83 changes: 63 additions & 20 deletions test/faker.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
import type { MockInstance } from 'vitest';
import { describe, expect, it, vi } from 'vitest';
import { Faker, faker } from '../src';
import { Faker, faker, generateMersenne32Randomizer } from '../src';
import { FakerError } from '../src/errors/faker-error';
import { keys } from '../src/internal/keys';

describe('faker', () => {
it('should throw error if no locales passed', () => {
expect(() => new Faker({ locale: [] })).toThrow(
new FakerError(
'The locale option must contain at least one locale definition.'
)
);
});

it('should not log anything on startup', async () => {
const spies: MockInstance[] = keys(console)
.filter((key) => typeof console[key] === 'function')
Expand Down Expand Up @@ -69,19 +61,70 @@ describe('faker', () => {
});
});

describe('randomizer', () => {
it('should be possible to provide a custom Randomizer', () => {
const customFaker = new Faker({
locale: {},
randomizer: {
next: () => 0,
seed: () => void 0,
},
describe('constructor()', () => {
describe('locale', () => {
it('should throw error if no locales passed', () => {
expect(() => new Faker({ locale: [] })).toThrow(
new FakerError(
'The locale option must contain at least one locale definition.'
)
);
});
});

describe('randomizer', () => {
it('should be possible to provide a custom Randomizer', () => {
const customFaker = new Faker({
locale: {},
randomizer: {
next: () => 0,
seed: () => void 0,
},
});

expect(customFaker.number.int()).toBe(0);
expect(customFaker.number.int()).toBe(0);
expect(customFaker.number.int()).toBe(0);
});
});

describe('seed', () => {
it('should be possible to provide an initial seed', () => {
const customFaker = new Faker({
locale: {},
seed: 12345,
});

expect(customFaker.number.int()).toBe(8373237378417847);
expect(customFaker.number.int()).toBe(2849657659447330);
expect(customFaker.number.int()).toBe(1656593383470774);

customFaker.seed(12345);

expect(customFaker.number.int()).toBe(8373237378417847);
expect(customFaker.number.int()).toBe(2849657659447330);
expect(customFaker.number.int()).toBe(1656593383470774);
});
});

expect(customFaker.number.int()).toBe(0);
expect(customFaker.number.int()).toBe(0);
expect(customFaker.number.int()).toBe(0);
describe('randomizer+seed', () => {
it('should take apply both the randomizer and seed', () => {
const customFaker = new Faker({
locale: {},
randomizer: generateMersenne32Randomizer(67890),
seed: 12345,
});

expect(customFaker.number.int()).toBe(8373237322874880);
expect(customFaker.number.int()).toBe(8017800868134912);
expect(customFaker.number.int()).toBe(2849657711493120);

customFaker.seed(12345); // Retry with the expected seed

expect(customFaker.number.int()).toBe(8373237322874880);
expect(customFaker.number.int()).toBe(8017800868134912);
expect(customFaker.number.int()).toBe(2849657711493120);
});
});
});

Expand Down
11 changes: 11 additions & 0 deletions test/internal/seed.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { describe, expect, it } from 'vitest';
import { randomSeed } from '../../src/internal/seed';

describe('seed', () => {
it('should generate a random seed', () => {
const actual = randomSeed();

expect(actual).toBeTypeOf('number');
expect(actual).not.toBe(randomSeed());
});
});
56 changes: 55 additions & 1 deletion test/simple-faker.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { MockInstance } from 'vitest';
import { describe, expect, it, vi } from 'vitest';
import { SimpleFaker, simpleFaker } from '../src';
import { generateMersenne32Randomizer, SimpleFaker, simpleFaker } from '../src';
import { keys } from '../src/internal/keys';

describe('simpleFaker', () => {
Expand All @@ -20,6 +20,60 @@ describe('simpleFaker', () => {
}
});

describe('constructor()', () => {
describe('randomizer', () => {
it('should be possible to provide a custom Randomizer', () => {
const customFaker = new SimpleFaker({
randomizer: {
next: () => 0,
seed: () => void 0,
},
});

expect(customFaker.number.int()).toBe(0);
expect(customFaker.number.int()).toBe(0);
expect(customFaker.number.int()).toBe(0);
});
});

describe('seed', () => {
it('should be possible to provide an initial seed', () => {
const customFaker = new SimpleFaker({
seed: 12345,
});

expect(customFaker.number.int()).toBe(8373237378417847);
expect(customFaker.number.int()).toBe(2849657659447330);
expect(customFaker.number.int()).toBe(1656593383470774);

customFaker.seed(12345); // Retry with the expected seed

expect(customFaker.number.int()).toBe(8373237378417847);
expect(customFaker.number.int()).toBe(2849657659447330);
expect(customFaker.number.int()).toBe(1656593383470774);
});
});

describe('randomizer+seed', () => {
it('should take apply both the randomizer and seed', () => {
const customFaker = new SimpleFaker({
randomizer: generateMersenne32Randomizer(67890),
seed: 12345,
});

expect(customFaker.number.int()).toBe(8373237322874880);
expect(customFaker.number.int()).toBe(8017800868134912);
expect(customFaker.number.int()).toBe(2849657711493120);

customFaker.seed(12345); // Retry with the expected seed

expect(customFaker.number.int()).toBe(8373237322874880);
expect(customFaker.number.int()).toBe(8017800868134912);
expect(customFaker.number.int()).toBe(2849657711493120);
});
});
});

// This is only here for coverage
// The actual test is in mersenne.spec.ts
describe('seed()', () => {
Expand Down

0 comments on commit 1633c8d

Please sign in to comment.