From eeeaae24aee216c1bfc2925d787080e5c79d20a4 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Fri, 25 Oct 2024 01:03:17 +0200 Subject: [PATCH 1/6] feat: add initial seed parameter to constructors --- src/faker.ts | 19 ++++++++- src/internal/seed.ts | 8 ++++ src/simple-faker.ts | 27 +++++++++++-- src/utils/mersenne.ts | 17 ++++++-- test/faker.spec.ts | 81 ++++++++++++++++++++++++++++---------- test/internal/seed.spec.ts | 11 ++++++ test/simple-faker.spec.ts | 54 ++++++++++++++++++++++++- 7 files changed, 187 insertions(+), 30 deletions(-) create mode 100644 src/internal/seed.ts create mode 100644 test/internal/seed.spec.ts diff --git a/src/faker.ts b/src/faker.ts index 77630d29ee7..a70d29e7472 100644 --- a/src/faker.ts +++ b/src/faker.ts @@ -125,6 +125,11 @@ 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. + * This option is ignored if `randomizer` is specified. + * Defaults to a random seed. * * @example * import { Faker, es } from '@faker-js/faker'; @@ -157,8 +162,20 @@ 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. + * + * This option is ignored if `randomizer` is specified. + * + * @default randomSeed() + */ + seed?: number; }) { - super({ randomizer: options.randomizer }); + super({ seed: options.seed, randomizer: options.randomizer }); let { locale } = options; diff --git a/src/internal/seed.ts b/src/internal/seed.ts new file mode 100644 index 00000000000..3ff484b2768 --- /dev/null +++ b/src/internal/seed.ts @@ -0,0 +1,8 @@ +/** + * Generates a random seed. + * + * @internal + */ +export function randomSeed(): number { + return Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER); +} diff --git a/src/simple-faker.ts b/src/simple-faker.ts index 743df923570..79faf05c942 100644 --- a/src/simple-faker.ts +++ b/src/simple-faker.ts @@ -1,3 +1,4 @@ +import { randomSeed } from './internal/seed'; import { DatatypeModule } from './modules/datatype'; import { SimpleDateModule } from './modules/date'; import { SimpleHelpersModule } from './modules/helpers'; @@ -97,6 +98,11 @@ 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. + * This option is ignored if `randomizer` is specified. + * Defaults to a random seed. * * @example * import { SimpleFaker } from '@faker-js/faker'; @@ -120,9 +126,24 @@ 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. + * + * This option is ignored if `randomizer` is specified. + * + * @default randomSeed() + */ + seed?: number; } = {} ) { - const { randomizer = generateMersenne53Randomizer() } = options; + const { + seed = randomSeed(), + randomizer = generateMersenne53Randomizer(seed), + } = options; this._randomizer = randomizer; } @@ -247,9 +268,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; diff --git a/src/utils/mersenne.ts b/src/utils/mersenne.ts index de415f9166e..8aa525cf21a 100644 --- a/src/utils/mersenne.ts +++ b/src/utils/mersenne.ts @@ -1,14 +1,19 @@ 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. */ -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 { @@ -27,11 +32,15 @@ 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. */ -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 { diff --git a/test/faker.spec.ts b/test/faker.spec.ts index 6d928d63652..78d4c5f82f6 100644 --- a/test/faker.spec.ts +++ b/test/faker.spec.ts @@ -1,18 +1,10 @@ import type { MockInstance } from 'vitest'; import { describe, expect, it, vi } from 'vitest'; -import { Faker, faker } from '../src'; +import { Faker, faker, generateMersenne53Randomizer } 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') @@ -69,19 +61,68 @@ 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); - expect(customFaker.number.int()).toBe(0); - expect(customFaker.number.int()).toBe(0); - expect(customFaker.number.int()).toBe(0); + customFaker.seed(12345); + + expect(customFaker.number.int()).toBe(8373237378417847); + expect(customFaker.number.int()).toBe(2849657659447330); + expect(customFaker.number.int()).toBe(1656593383470774); + }); + + it('should prioritize the randomizer over the seed', () => { + const customFaker = new Faker({ + locale: {}, + randomizer: generateMersenne53Randomizer(67890), + seed: 12345, // This seed should be ignored + }); + + expect(customFaker.number.int()).toBe(3319821087749105); + expect(customFaker.number.int()).toBe(8108180265059478); + expect(customFaker.number.int()).toBe(1714153343835993); + + customFaker.seed(67890); + + expect(customFaker.number.int()).toBe(3319821087749105); + expect(customFaker.number.int()).toBe(8108180265059478); + expect(customFaker.number.int()).toBe(1714153343835993); + }); }); }); diff --git a/test/internal/seed.spec.ts b/test/internal/seed.spec.ts new file mode 100644 index 00000000000..c3503524c8d --- /dev/null +++ b/test/internal/seed.spec.ts @@ -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()); + }); +}); diff --git a/test/simple-faker.spec.ts b/test/simple-faker.spec.ts index aa6f00c1892..9bbfad30d99 100644 --- a/test/simple-faker.spec.ts +++ b/test/simple-faker.spec.ts @@ -1,6 +1,6 @@ import type { MockInstance } from 'vitest'; import { describe, expect, it, vi } from 'vitest'; -import { SimpleFaker, simpleFaker } from '../src'; +import { generateMersenne53Randomizer, SimpleFaker, simpleFaker } from '../src'; import { keys } from '../src/internal/keys'; describe('simpleFaker', () => { @@ -20,6 +20,58 @@ 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); + }); + + it('should prioritize the randomizer over the seed', () => { + const customFaker = new SimpleFaker({ + randomizer: generateMersenne53Randomizer(67890), + seed: 12345, // This seed should be ignored + }); + + expect(customFaker.number.int()).toBe(3319821087749105); + expect(customFaker.number.int()).toBe(8108180265059478); + expect(customFaker.number.int()).toBe(1714153343835993); + + customFaker.seed(67890); // Retry with the expected seed + + expect(customFaker.number.int()).toBe(3319821087749105); + expect(customFaker.number.int()).toBe(8108180265059478); + expect(customFaker.number.int()).toBe(1714153343835993); + }); + }); + }); + // This is only here for coverage // The actual test is in mersenne.spec.ts describe('seed()', () => { From f9d09576d3208d8c41eb2e3c5c4b0affbed0d861 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Sat, 26 Oct 2024 02:28:02 +0200 Subject: [PATCH 2/6] chore: apply suggestions --- src/faker.ts | 3 --- src/simple-faker.ts | 9 ++++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/faker.ts b/src/faker.ts index a70d29e7472..d3b60e7b2ac 100644 --- a/src/faker.ts +++ b/src/faker.ts @@ -128,7 +128,6 @@ export class Faker extends SimpleFaker { * @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. - * This option is ignored if `randomizer` is specified. * Defaults to a random seed. * * @example @@ -169,8 +168,6 @@ export class Faker extends SimpleFaker { * * Refer to the `seed()` method for more information. * - * This option is ignored if `randomizer` is specified. - * * @default randomSeed() */ seed?: number; diff --git a/src/simple-faker.ts b/src/simple-faker.ts index 79faf05c942..b74a4465241 100644 --- a/src/simple-faker.ts +++ b/src/simple-faker.ts @@ -101,7 +101,6 @@ export class SimpleFaker { * @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. - * This option is ignored if `randomizer` is specified. * Defaults to a random seed. * * @example @@ -133,18 +132,22 @@ export class SimpleFaker { * * Refer to the `seed()` method for more information. * - * This option is ignored if `randomizer` is specified. - * * @default randomSeed() */ seed?: number; } = {} ) { const { + seed: originalSeed, seed = randomSeed(), + randomizer: originalRandomizer, randomizer = generateMersenne53Randomizer(seed), } = options; + if (originalRandomizer != null && originalSeed != null) { + randomizer.seed(seed); + } + this._randomizer = randomizer; } From d1a313c27c615c6e4e946698f130c398f330d1dc Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Sat, 26 Oct 2024 02:32:31 +0200 Subject: [PATCH 3/6] chore: simplify --- src/simple-faker.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/simple-faker.ts b/src/simple-faker.ts index b74a4465241..b7ddcc578e3 100644 --- a/src/simple-faker.ts +++ b/src/simple-faker.ts @@ -137,18 +137,13 @@ export class SimpleFaker { seed?: number; } = {} ) { - const { - seed: originalSeed, - seed = randomSeed(), - randomizer: originalRandomizer, - randomizer = generateMersenne53Randomizer(seed), - } = options; + const { seed, randomizer } = options; - if (originalRandomizer != null && originalSeed != null) { + if (seed != null && randomizer != null) { randomizer.seed(seed); } - this._randomizer = randomizer; + this._randomizer = randomizer ?? generateMersenne53Randomizer(seed); } /** From 3d6c42eb71652ae2e5f7076d0aac15d48522dd5b Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Sat, 26 Oct 2024 02:34:46 +0200 Subject: [PATCH 4/6] chore: sort --- src/faker.ts | 2 +- src/simple-faker.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/faker.ts b/src/faker.ts index d3b60e7b2ac..a360a59b1c8 100644 --- a/src/faker.ts +++ b/src/faker.ts @@ -172,7 +172,7 @@ export class Faker extends SimpleFaker { */ seed?: number; }) { - super({ seed: options.seed, randomizer: options.randomizer }); + super({ randomizer: options.randomizer, seed: options.seed }); let { locale } = options; diff --git a/src/simple-faker.ts b/src/simple-faker.ts index b7ddcc578e3..fd89f6c0bca 100644 --- a/src/simple-faker.ts +++ b/src/simple-faker.ts @@ -137,9 +137,9 @@ export class SimpleFaker { seed?: number; } = {} ) { - const { seed, randomizer } = options; + const { randomizer, seed } = options; - if (seed != null && randomizer != null) { + if (randomizer != null && seed != null) { randomizer.seed(seed); } From b77af40411cee001ac39d2fed062fdf7bd6cf79d Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Sat, 26 Oct 2024 02:45:14 +0200 Subject: [PATCH 5/6] chore: fix tests --- test/faker.spec.ts | 24 +++++++++++++----------- test/simple-faker.spec.ts | 24 +++++++++++++----------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/test/faker.spec.ts b/test/faker.spec.ts index 78d4c5f82f6..dba19421912 100644 --- a/test/faker.spec.ts +++ b/test/faker.spec.ts @@ -1,6 +1,6 @@ import type { MockInstance } from 'vitest'; import { describe, expect, it, vi } from 'vitest'; -import { Faker, faker, generateMersenne53Randomizer } from '../src'; +import { Faker, faker, generateMersenne32Randomizer } from '../src'; import { FakerError } from '../src/errors/faker-error'; import { keys } from '../src/internal/keys'; @@ -105,23 +105,25 @@ describe('faker', () => { expect(customFaker.number.int()).toBe(2849657659447330); expect(customFaker.number.int()).toBe(1656593383470774); }); + }); - it('should prioritize the randomizer over the seed', () => { + describe('randomizer+seed', () => { + it('should take apply both the randomizer and seed', () => { const customFaker = new Faker({ locale: {}, - randomizer: generateMersenne53Randomizer(67890), - seed: 12345, // This seed should be ignored + randomizer: generateMersenne32Randomizer(67890), + seed: 12345, }); - expect(customFaker.number.int()).toBe(3319821087749105); - expect(customFaker.number.int()).toBe(8108180265059478); - expect(customFaker.number.int()).toBe(1714153343835993); + expect(customFaker.number.int()).toBe(8373237322874880); + expect(customFaker.number.int()).toBe(8017800868134912); + expect(customFaker.number.int()).toBe(2849657711493120); - customFaker.seed(67890); + customFaker.seed(12345); // Retry with the expected seed - expect(customFaker.number.int()).toBe(3319821087749105); - expect(customFaker.number.int()).toBe(8108180265059478); - expect(customFaker.number.int()).toBe(1714153343835993); + expect(customFaker.number.int()).toBe(8373237322874880); + expect(customFaker.number.int()).toBe(8017800868134912); + expect(customFaker.number.int()).toBe(2849657711493120); }); }); }); diff --git a/test/simple-faker.spec.ts b/test/simple-faker.spec.ts index 9bbfad30d99..02c860a50ee 100644 --- a/test/simple-faker.spec.ts +++ b/test/simple-faker.spec.ts @@ -1,6 +1,6 @@ import type { MockInstance } from 'vitest'; import { describe, expect, it, vi } from 'vitest'; -import { generateMersenne53Randomizer, SimpleFaker, simpleFaker } from '../src'; +import { generateMersenne32Randomizer, SimpleFaker, simpleFaker } from '../src'; import { keys } from '../src/internal/keys'; describe('simpleFaker', () => { @@ -52,22 +52,24 @@ describe('simpleFaker', () => { expect(customFaker.number.int()).toBe(2849657659447330); expect(customFaker.number.int()).toBe(1656593383470774); }); + }); - it('should prioritize the randomizer over the seed', () => { + describe('randomizer+seed', () => { + it('should take apply both the randomizer and seed', () => { const customFaker = new SimpleFaker({ - randomizer: generateMersenne53Randomizer(67890), - seed: 12345, // This seed should be ignored + randomizer: generateMersenne32Randomizer(67890), + seed: 12345, }); - expect(customFaker.number.int()).toBe(3319821087749105); - expect(customFaker.number.int()).toBe(8108180265059478); - expect(customFaker.number.int()).toBe(1714153343835993); + expect(customFaker.number.int()).toBe(8373237322874880); + expect(customFaker.number.int()).toBe(8017800868134912); + expect(customFaker.number.int()).toBe(2849657711493120); - customFaker.seed(67890); // Retry with the expected seed + customFaker.seed(12345); // Retry with the expected seed - expect(customFaker.number.int()).toBe(3319821087749105); - expect(customFaker.number.int()).toBe(8108180265059478); - expect(customFaker.number.int()).toBe(1714153343835993); + expect(customFaker.number.int()).toBe(8373237322874880); + expect(customFaker.number.int()).toBe(8017800868134912); + expect(customFaker.number.int()).toBe(2849657711493120); }); }); }); From 604e144d6301d0f96a7a1aa28a79b57241f8034f Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Sat, 26 Oct 2024 18:01:15 +0200 Subject: [PATCH 6/6] chore: apply suggestions --- src/faker.ts | 2 +- src/simple-faker.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/faker.ts b/src/faker.ts index a360a59b1c8..0b03788b3be 100644 --- a/src/faker.ts +++ b/src/faker.ts @@ -168,7 +168,7 @@ export class Faker extends SimpleFaker { * * Refer to the `seed()` method for more information. * - * @default randomSeed() + * Defaults to a random seed. */ seed?: number; }) { diff --git a/src/simple-faker.ts b/src/simple-faker.ts index fd89f6c0bca..1371e68a773 100644 --- a/src/simple-faker.ts +++ b/src/simple-faker.ts @@ -132,7 +132,7 @@ export class SimpleFaker { * * Refer to the `seed()` method for more information. * - * @default randomSeed() + * Defaults to a random seed. */ seed?: number; } = {}