aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorST-DDT <[email protected]>2023-12-01 18:10:05 +0100
committerGitHub <[email protected]>2023-12-01 17:10:05 +0000
commit2eca5bc06c5f53efe9fe495577cd930620453794 (patch)
tree6a4efd5c42e7bb08261e7b11eb70f58a90fab6b3
parent5e0f4f7dd1497e80c2e725a7bee3887abaab25a7 (diff)
downloadfaker-2eca5bc06c5f53efe9fe495577cd930620453794.tar.xz
faker-2eca5bc06c5f53efe9fe495577cd930620453794.zip
test(mersenne): add tests for value ranges (#2470)
-rw-r--r--src/internal/mersenne.ts2
-rw-r--r--test/internal/__snapshots__/mersenne.spec.ts.snap (renamed from test/__snapshots__/mersenne.spec.ts.snap)0
-rw-r--r--test/internal/mersenne-test-utils.ts17
-rw-r--r--test/internal/mersenne.spec.ts136
-rw-r--r--test/mersenne.spec.ts57
-rw-r--r--test/modules/number.spec.ts37
6 files changed, 190 insertions, 59 deletions
diff --git a/src/internal/mersenne.ts b/src/internal/mersenne.ts
index 6be5825d..2372e364 100644
--- a/src/internal/mersenne.ts
+++ b/src/internal/mersenne.ts
@@ -73,7 +73,7 @@ import type { Randomizer } from '../randomizer';
*
* @internal
*/
-class MersenneTwister19937 {
+export class MersenneTwister19937 {
private readonly N = 624;
private readonly M = 397;
private readonly MATRIX_A = 0x9908b0df; // constant vector a
diff --git a/test/__snapshots__/mersenne.spec.ts.snap b/test/internal/__snapshots__/mersenne.spec.ts.snap
index c55b1152..c55b1152 100644
--- a/test/__snapshots__/mersenne.spec.ts.snap
+++ b/test/internal/__snapshots__/mersenne.spec.ts.snap
diff --git a/test/internal/mersenne-test-utils.ts b/test/internal/mersenne-test-utils.ts
new file mode 100644
index 00000000..ee7a43da
--- /dev/null
+++ b/test/internal/mersenne-test-utils.ts
@@ -0,0 +1,17 @@
+// Moved to a separate file to avoid importing the tests
+
+/**
+ * The maximum value that can be returned by `MersenneTwister19937.genrandReal2()`.
+ * This is the max possible value with 32 bits of precision that is less than 1.
+ */
+export const TWISTER_32CO_MAX_VALUE = 0.9999999997671694;
+/**
+ * The maximum value that can be returned by `MersenneTwister19937.genrandRes53()`.
+ * This is the max possible value with 53 bits of precision that is less than 1.
+ */
+export const TWISTER_53CO_MAX_VALUE = 0.9999999999999999;
+// Re-exported because the value might change in the future
+/**
+ * The maximum value that can be returned by `next()`.
+ */
+export const MERSENNE_MAX_VALUE = TWISTER_32CO_MAX_VALUE;
diff --git a/test/internal/mersenne.spec.ts b/test/internal/mersenne.spec.ts
new file mode 100644
index 00000000..bbee0502
--- /dev/null
+++ b/test/internal/mersenne.spec.ts
@@ -0,0 +1,136 @@
+import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
+import {
+ generateMersenne32Randomizer,
+ MersenneTwister19937,
+} from '../../src/internal/mersenne';
+import type { Randomizer } from '../../src/randomizer';
+import { seededRuns } from '../support/seeded-runs';
+import { times } from '../support/times';
+import {
+ MERSENNE_MAX_VALUE,
+ TWISTER_32CO_MAX_VALUE,
+ TWISTER_53CO_MAX_VALUE,
+} from './mersenne-test-utils';
+
+const NON_SEEDED_BASED_RUN = 25;
+
+function newTwister(
+ seed: number = Math.random() * Number.MAX_SAFE_INTEGER
+): MersenneTwister19937 {
+ const twister = new MersenneTwister19937();
+ twister.initGenrand(seed);
+ return twister;
+}
+
+describe('MersenneTwister19937', () => {
+ describe('genrandInt32()', () => {
+ it('should be able to return 0', () => {
+ const twister = newTwister(257678572);
+
+ // There is no single value seed that can produce 0 in the first call
+ for (let i = 0; i < 5; i++) {
+ twister.genrandInt32();
+ }
+
+ const actual = twister.genrandInt32();
+ expect(actual).toBe(0);
+ });
+
+ it('should be able to return 2^32-1', () => {
+ const twister = newTwister(2855577693);
+ const actual = twister.genrandInt32();
+ expect(actual).toBe(2 ** 32 - 1);
+ });
+ });
+
+ describe('genrandReal2()', () => {
+ it('should be able to return 0', () => {
+ const twister = newTwister();
+ // shortcut to return minimal value
+ // the test above shows that it is possible to return 0
+ twister.genrandInt32 = () => 0;
+ const actual = twister.genrandReal2();
+ expect(actual).toBe(0);
+ });
+
+ it('should be able to return almost 1', () => {
+ const twister = newTwister();
+ // shortcut to return maximal value
+ // the test above shows that it is possible to return 2^32-1
+ twister.genrandInt32 = () => 2 ** 32 - 1;
+ const actual = twister.genrandReal2();
+ expect(actual).toBe(TWISTER_32CO_MAX_VALUE);
+ });
+ });
+
+ describe('genrandRes53()', () => {
+ it('should be able to return 0', () => {
+ const twister = newTwister();
+ // shortcut to return minimal value
+ // the test above shows that it is possible to return 0
+ twister.genrandInt32 = () => 0;
+ const actual = twister.genrandRes53();
+ expect(actual).toBe(0);
+ });
+
+ it('should be able to return almost 1', () => {
+ const twister = newTwister();
+ // shortcut to return maximal value
+ // the test above shows that it is possible to return 2^32-1
+ twister.genrandInt32 = () => 2 ** 32 - 1;
+ const actual = twister.genrandRes53();
+ expect(actual).toBe(TWISTER_53CO_MAX_VALUE);
+ });
+ });
+});
+
+describe('generateMersenne32Randomizer()', () => {
+ const randomizer: Randomizer = generateMersenne32Randomizer();
+
+ it('should return a result matching the interface', () => {
+ expect(randomizer).toBeDefined();
+ expect(randomizer).toBeTypeOf('object');
+ expect(randomizer.next).toBeTypeOf('function');
+ expect(randomizer.seed).toBeTypeOf('function');
+ });
+
+ describe.each(
+ [...seededRuns, ...seededRuns.map((v) => [v, 1, 2])].map((v) => [v])
+ )('seed: %j', (seed) => {
+ beforeEach(() => {
+ randomizer.seed(seed);
+ });
+
+ it('should return deterministic value for next()', () => {
+ const actual = randomizer.next();
+
+ expect(actual).toMatchSnapshot();
+ });
+ });
+
+ function randomSeed(): number {
+ return Math.ceil(Math.random() * 1_000_000_000);
+ }
+
+ // Create and log-back the seed for debug purposes
+ describe.each(
+ times(NON_SEEDED_BASED_RUN).flatMap(() => [
+ [randomSeed()],
+ [[randomSeed(), randomSeed()]],
+ ])
+ )('random seeded tests %j', (seed) => {
+ beforeAll(() => {
+ randomizer.seed(seed);
+ });
+
+ describe('next', () => {
+ it('should return random number from interval [0, 1)', () => {
+ const actual = randomizer.next();
+
+ expect(actual).toBeGreaterThanOrEqual(0);
+ expect(actual).toBeLessThanOrEqual(MERSENNE_MAX_VALUE);
+ expect(actual).toBeLessThan(1);
+ });
+ });
+ });
+});
diff --git a/test/mersenne.spec.ts b/test/mersenne.spec.ts
deleted file mode 100644
index 6c7d5a18..00000000
--- a/test/mersenne.spec.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
-import { generateMersenne32Randomizer } from '../src/internal/mersenne';
-import type { Randomizer } from '../src/randomizer';
-import { seededRuns } from './support/seeded-runs';
-import { times } from './support/times';
-
-const NON_SEEDED_BASED_RUN = 25;
-
-describe('generateMersenne32Randomizer()', () => {
- const randomizer: Randomizer = generateMersenne32Randomizer();
-
- it('should return a result matching the interface', () => {
- expect(randomizer).toBeDefined();
- expect(randomizer).toBeTypeOf('object');
- expect(randomizer.next).toBeTypeOf('function');
- expect(randomizer.seed).toBeTypeOf('function');
- });
-
- describe.each(
- [...seededRuns, ...seededRuns.map((v) => [v, 1, 2])].map((v) => [v])
- )('seed: %j', (seed) => {
- beforeEach(() => {
- randomizer.seed(seed);
- });
-
- it('should return deterministic value for next()', () => {
- const actual = randomizer.next();
-
- expect(actual).toMatchSnapshot();
- });
- });
-
- function randomSeed(): number {
- return Math.ceil(Math.random() * 1_000_000_000);
- }
-
- // Create and log-back the seed for debug purposes
- describe.each(
- times(NON_SEEDED_BASED_RUN).flatMap(() => [
- [randomSeed()],
- [[randomSeed(), randomSeed()]],
- ])
- )('random seeded tests %j', (seed) => {
- beforeAll(() => {
- randomizer.seed(seed);
- });
-
- describe('next', () => {
- it('should return random number from interval [0, 1)', () => {
- const actual = randomizer.next();
-
- expect(actual).toBeGreaterThanOrEqual(0);
- expect(actual).toBeLessThan(1);
- });
- });
- });
-});
diff --git a/test/modules/number.spec.ts b/test/modules/number.spec.ts
index 66018ee6..699200b2 100644
--- a/test/modules/number.spec.ts
+++ b/test/modules/number.spec.ts
@@ -1,6 +1,7 @@
import validator from 'validator';
import { describe, expect, it } from 'vitest';
-import { faker, FakerError } from '../../src';
+import { faker, FakerError, SimpleFaker } from '../../src';
+import { MERSENNE_MAX_VALUE } from '../internal/mersenne-test-utils';
import { seededTests } from '../support/seeded-runs';
describe('number', () => {
@@ -507,4 +508,38 @@ describe('number', () => {
});
});
});
+
+ describe('value range tests', () => {
+ const customFaker = new SimpleFaker();
+ // @ts-expect-error: access private member field
+ const randomizer = customFaker._randomizer;
+ describe('int', () => {
+ it('should be able to return 0', () => {
+ randomizer.next = () => 0;
+ const actual = customFaker.number.int();
+ expect(actual).toBe(0);
+ });
+
+ // TODO @ST-DDT 2023-10-12: This requires a randomizer with 53 bits of precision
+ it.todo('should be able to return MAX_SAFE_INTEGER', () => {
+ randomizer.next = () => MERSENNE_MAX_VALUE;
+ const actual = customFaker.number.int();
+ expect(actual).toBe(Number.MAX_SAFE_INTEGER);
+ });
+ });
+
+ describe('float', () => {
+ it('should be able to return 0', () => {
+ randomizer.next = () => 0;
+ const actual = customFaker.number.float();
+ expect(actual).toBe(0);
+ });
+
+ it('should be able to return almost 1', () => {
+ randomizer.next = () => MERSENNE_MAX_VALUE;
+ const actual = customFaker.number.float();
+ expect(actual).toBe(MERSENNE_MAX_VALUE);
+ });
+ });
+ });
});