Files
wehub-resource-sync 311a3666c4
Build documentation / build (push) Failing after 0s
Unit tests / build (18) (push) Has been cancelled
Unit tests / build (20) (push) Has been cancelled
Unit tests / build (22) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:44:39 +08:00

341 lines
12 KiB
JavaScript

import { random } from "../../src/utils/random.js";
import { init } from "../init.js";
// Initialise the testing environment
init();
/**
* All expected values below were generated by running the equivalent Python code
* using CPython's `random` module (Mersenne Twister 19937). For example:
*
* ```python
* import random
* random.seed(42)
* [random.random() for _ in range(10)]
* ```
*
* The JS implementation must match Python's output exactly for reproducibility.
*/
describe("random (Mersenne Twister 19937, Python-compatible)", () => {
// Must be the first test — verifies auto-seeding at module load produces non-zero output.
it("should return non-zero values before any explicit seed() call", () => {
const value = random.random();
expect(value).not.toBe(0);
expect(value).toBeGreaterThanOrEqual(0);
expect(value).toBeLessThan(1);
});
describe("random()", () => {
it("should produce the correct sequence for seed(42)", () => {
const expected = [0.6394267984578837, 0.025010755222666936, 0.27502931836911926, 0.22321073814882275, 0.7364712141640124, 0.6766994874229113, 0.8921795677048454, 0.08693883262941615, 0.4219218196852704, 0.029797219438070344];
random.seed(42);
for (let i = 0; i < expected.length; i++) {
expect(random.random()).toBe(expected[i]);
}
});
it("should produce the correct sequence for seed(0)", () => {
const expected = [0.8444218515250481, 0.7579544029403025, 0.420571580830845, 0.25891675029296335, 0.5112747213686085, 0.4049341374504143, 0.7837985890347726, 0.30331272607892745, 0.4765969541523558, 0.5833820394550312];
random.seed(0);
for (let i = 0; i < expected.length; i++) {
expect(random.random()).toBe(expected[i]);
}
});
it("should produce the correct sequence for seed(12345)", () => {
const expected = [0.41661987254534116, 0.010169169457068361, 0.8252065092537432, 0.2986398551995928, 0.3684116894884757, 0.19366134904507426, 0.5660081687288613, 0.1616878239293682, 0.12426688428353017, 0.4329362680099159];
random.seed(12345);
for (let i = 0; i < expected.length; i++) {
expect(random.random()).toBe(expected[i]);
}
});
it("should handle a large seed (2^40 + 17)", () => {
const expected = [0.6614192930187859, 0.24151842637090726, 0.6909905728254405, 0.11131596896699081, 0.8184932948202619, 0.4957890371902045, 0.3754628980018908, 0.6967482542555166, 0.5464037278028259, 0.4648564722045134];
random.seed(2 ** 40 + 17);
for (let i = 0; i < expected.length; i++) {
expect(random.random()).toBe(expected[i]);
}
});
it("should always return values in [0, 1)", () => {
random.seed(777);
for (let i = 0; i < 10000; i++) {
const v = random.random();
expect(v).toBeGreaterThanOrEqual(0);
expect(v).toBeLessThan(1);
}
});
});
describe("determinism", () => {
it("should produce the same sequence when re-seeded with the same value", () => {
random.seed(42);
const first = Array.from({ length: 20 }, () => random.random());
random.seed(42);
const second = Array.from({ length: 20 }, () => random.random());
expect(first).toEqual(second);
});
it("should produce different sequences for different seeds", () => {
random.seed(1);
const seq1 = Array.from({ length: 10 }, () => random.random());
random.seed(2);
const seq2 = Array.from({ length: 10 }, () => random.random());
expect(seq1).not.toEqual(seq2);
});
});
describe("gauss()", () => {
it("should produce the correct sequence for seed(42) with default mu=0, sigma=1", () => {
const expected = [-0.14409032957792836, -0.1729036003315193, -0.11131586156766246, 0.7019837250988631, -0.12758828378288709, -1.4973534143409575, 0.33231834406771527, -0.2673374784971682, -0.216958684145195, 0.11588478670085507];
random.seed(42);
for (let i = 0; i < expected.length; i++) {
expect(random.gauss()).toBe(expected[i]);
}
});
it("should produce the correct sequence for seed(99) with mu=5, sigma=2", () => {
const expected = [3.8995546045187948, 5.758236653956967, 5.653854573190089, 6.362796994590186, 5.0943415236981435, 3.4818290248514048, 2.746397968893985, 7.035976389331746, 0.4167692939861194, 3.8652143740326643];
random.seed(99);
for (let i = 0; i < expected.length; i++) {
expect(random.gauss(5, 2)).toBe(expected[i]);
}
});
it("should have approximately zero mean and unit variance over many samples", () => {
random.seed(314159);
const n = 50000;
let sum = 0;
let sumSq = 0;
for (let i = 0; i < n; i++) {
const v = random.gauss();
sum += v;
sumSq += v * v;
}
const mean = sum / n;
const variance = sumSq / n - mean * mean;
expect(Math.abs(mean)).toBeLessThan(0.02);
expect(Math.abs(variance - 1)).toBeLessThan(0.05);
});
});
describe("shuffle()", () => {
it("should produce the correct permutation for seed(42) on [0..9]", () => {
const expected = [7, 3, 2, 8, 5, 6, 9, 4, 0, 1];
const arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
random.seed(42);
random.shuffle(arr);
expect(arr).toEqual(expected);
});
it("should produce the correct permutation for seed(7) on letters", () => {
const expected = ["i", "d", "b", "e", "h", "a", "j", "g", "c", "f"];
const arr = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"];
random.seed(7);
random.shuffle(arr);
expect(arr).toEqual(expected);
});
it("should preserve all elements (no duplicates, no missing)", () => {
const original = Array.from({ length: 100 }, (_, i) => i);
const arr = [...original];
random.seed(2025);
random.shuffle(arr);
expect(arr.sort((a, b) => a - b)).toEqual(original);
});
it("should be a no-op for single-element and empty arrays", () => {
const single = [42];
random.seed(0);
random.shuffle(single);
expect(single).toEqual([42]);
const empty = [];
random.shuffle(empty);
expect(empty).toEqual([]);
});
});
describe("choices()", () => {
it("should produce the correct picks for seed(42) with weights [10,1,1,1]", () => {
const expected = ["a", "a", "a", "a", "a", "a", "c", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "a", "b", "a"];
const population = ["a", "b", "c", "d"];
const weights = [10, 1, 1, 1];
random.seed(42);
for (let i = 0; i < expected.length; i++) {
expect(random.choices(population, weights)).toBe(expected[i]);
}
});
it("should produce the correct picks for seed(42) with float weights", () => {
// Python: random.seed(42); [random.choices([0,1,2,3,4], weights=[0.1,0.2,0.3,0.25,0.15])[0] for _ in range(20)]
const expected = [3, 0, 1, 1, 3, 3, 4, 0, 2, 0, 1, 2, 0, 1, 3, 2, 1, 2, 3, 0];
const population = [0, 1, 2, 3, 4];
const weights = [0.1, 0.2, 0.3, 0.25, 0.15];
random.seed(42);
for (let i = 0; i < expected.length; i++) {
expect(random.choices(population, weights)).toBe(expected[i]);
}
});
it("should produce the correct picks for seed(12345) with uniform weights", () => {
// Python: random.seed(12345); [random.choices(['x','y','z'], weights=[1,1,1])[0] for _ in range(15)]
const expected = ["y", "x", "z", "x", "y", "x", "y", "x", "x", "y", "y", "x", "y", "y", "z"];
const population = ["x", "y", "z"];
const weights = [1, 1, 1];
random.seed(12345);
for (let i = 0; i < expected.length; i++) {
expect(random.choices(population, weights)).toBe(expected[i]);
}
});
it("should only ever return elements from the population", () => {
const population = [10, 20, 30];
const weights = [1, 1, 1];
random.seed(123);
for (let i = 0; i < 1000; i++) {
expect(population).toContain(random.choices(population, weights));
}
});
it("should respect heavily skewed weights", () => {
// With weight 1000 on "x" and 1 on "y", nearly all picks should be "x"
const population = ["x", "y"];
const weights = [1000, 1];
random.seed(555);
let xCount = 0;
const n = 5000;
for (let i = 0; i < n; i++) {
if (random.choices(population, weights) === "x") xCount++;
}
expect(xCount / n).toBeGreaterThan(0.99);
});
it("should work with a single-element population", () => {
random.seed(0);
expect(random.choices(["only"], [1])).toBe("only");
expect(random.choices(["only"], [999])).toBe("only");
});
});
describe("Random class", () => {
it("should be accessible as random.Random", () => {
expect(typeof random.Random).toBe("function");
});
it("should produce the same sequence as the global functions given the same seed", () => {
const rng = new random.Random(42);
random.seed(42);
for (let i = 0; i < 20; i++) {
expect(rng.random()).toBe(random.random());
}
});
it("should be independent from the global state — seeding instance does not affect global", () => {
random.seed(42);
const before = random.random(); // consume one global value
expect(before).toBe(0.6394267984578837);
const rng = new random.Random(99);
rng.random(); // consume from instance
rng.seed(0); // re-seed instance
rng.random(); // consume again
// global should continue from where it left off, unaffected
expect(random.random()).toBe(0.025010755222666936);
random.seed(42);
expect(random.random()).toBe(before);
});
it("should be independent from the global state — seeding global does not affect instance", () => {
const rng = new random.Random(42);
const v0 = rng.random();
random.seed(999); // re-seed global
random.random(); // consume global values
random.random();
// instance should continue from where it left off
const rng2 = new random.Random(42);
expect(rng2.random()).toBe(v0);
});
it("two instances with the same seed produce identical sequences", () => {
const rng1 = new random.Random(42);
const rng2 = new random.Random(42);
for (let i = 0; i < 20; i++) {
expect(rng1.random()).toBe(rng2.random());
}
});
it("two instances with different seeds produce different sequences", () => {
const rng1 = new random.Random(1);
const rng2 = new random.Random(2);
const seq1 = Array.from({ length: 10 }, () => rng1.random());
const seq2 = Array.from({ length: 10 }, () => rng2.random());
expect(seq1).not.toEqual(seq2);
});
it("two instances are independent — advancing one does not affect the other", () => {
const rng1 = new random.Random(42);
const rng2 = new random.Random(42);
// Advance rng1 by 100 values
for (let i = 0; i < 100; i++) rng1.random();
// rng2 should still be at the start
const rng3 = new random.Random(42);
expect(rng2.random()).toBe(rng3.random());
});
it("instance.seed() re-initialises state deterministically", () => {
const rng = new random.Random(42);
const first = Array.from({ length: 10 }, () => rng.random());
rng.seed(42);
const second = Array.from({ length: 10 }, () => rng.random());
expect(first).toEqual(second);
});
it("instance.seed() with no arguments seeds from entropy (non-zero output)", () => {
const rng = new random.Random();
const v = rng.random();
expect(v).toBeGreaterThanOrEqual(0);
expect(v).toBeLessThan(1);
});
it("instance.gauss() matches global gauss() for the same seed", () => {
const rng = new random.Random(42);
random.seed(42);
for (let i = 0; i < 10; i++) {
expect(rng.gauss(5, 2)).toBe(random.gauss(5, 2));
}
});
it("instance.shuffle() matches global shuffle() for the same seed", () => {
const arr1 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
const arr2 = [...arr1];
const rng = new random.Random(42);
rng.shuffle(arr1);
random.seed(42);
random.shuffle(arr2);
expect(arr1).toEqual(arr2);
});
it("instance.choices() matches global choices() for the same seed", () => {
const population = ["a", "b", "c", "d"];
const weights = [10, 1, 1, 1];
const rng = new random.Random(42);
random.seed(42);
for (let i = 0; i < 20; i++) {
expect(rng.choices(population, weights)).toBe(random.choices(population, weights));
}
});
});
});