aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorYawning Angel <yawning@schwanenlied.me>2025-11-25 14:17:51 +0900
committerYawning Angel <yawning@schwanenlied.me>2025-11-29 16:16:52 +0900
commitaa8e6e0938b6c1f86aea70a9f297f37d3f814398 (patch)
tree0b0451554f23e382a305487c1d1614371df4737c /tests
parentea1f1e275d7ba62afddd185532ec60024aa16b6b (diff)
tests: Add random number generator tests
Diffstat (limited to 'tests')
-rw-r--r--tests/core/math/rand/test_core_math_rand.odin120
-rw-r--r--tests/internal/test_chacha8rand.odin151
2 files changed, 267 insertions, 4 deletions
diff --git a/tests/core/math/rand/test_core_math_rand.odin b/tests/core/math/rand/test_core_math_rand.odin
index 392d3d241..814a1b9f8 100644
--- a/tests/core/math/rand/test_core_math_rand.odin
+++ b/tests/core/math/rand/test_core_math_rand.odin
@@ -1,19 +1,54 @@
package test_core_math_rand
+import "core:math"
import "core:math/rand"
import "core:testing"
-@test
-test_default_rand_determinism :: proc(t: ^testing.T) {
+Generator :: struct {
+ name: string,
+ gen: rand.Generator,
+ biased: bool,
+}
+
+@(test)
+test_prngs :: proc(t: ^testing.T) {
+ gens := []Generator {
+ {
+ "default",
+ rand.default_random_generator(),
+ false,
+ },
+ {
+ "pcg64",
+ rand.pcg_random_generator(), // Deprecated
+ true,
+ },
+ {
+ "xoshiro**",
+ rand.xoshiro256_random_generator(),
+ false,
+ },
+ }
+ for gen in gens {
+ rand_determinism(t, gen)
+ if !gen.biased {
+ rand_issue_5881(t, gen)
+ }
+ }
+}
+
+@(private = "file")
+rand_determinism :: proc(t: ^testing.T, rng: Generator) {
+ context.random_generator = rng.gen
rand.reset(13)
first_value := rand.int127()
rand.reset(13)
second_value := rand.int127()
- testing.expect(t, first_value == second_value, "Context default random number generator is non-deterministic.")
+ testing.expectf(t, first_value == second_value, "rng '%s' is non-deterministic.", rng.name)
}
-@test
+@(test)
test_default_rand_determinism_user_set :: proc(t: ^testing.T) {
rng_state_1 := rand.create(13)
rng_state_2 := rand.create(13)
@@ -33,3 +68,80 @@ test_default_rand_determinism_user_set :: proc(t: ^testing.T) {
testing.expect(t, first_value == second_value, "User-set default random number generator is non-deterministic.")
}
+
+@(private = "file")
+rand_issue_5881 :: proc(t:^testing.T, rng: Generator) {
+ // Tests issue #5881 https://github.com/odin-lang/Odin/issues/5881
+
+ // Bit balance and sign uniformity (modest samples to keep CI fast)
+ expect_u64_bit_balance(t, rng, 200_000)
+ expect_quaternion_sign_uniformity(t, rng, 200_000)
+}
+
+// Helper: compute chi-square statistic for counts vs equal-expected across k bins
+@(private = "file")
+chi_square_equal :: proc(counts: []int) -> f64 {
+ n := 0
+ for c in counts {
+ n += c
+ }
+ if n == 0 {
+ return 0
+ }
+ k := len(counts)
+ exp := f64(n) / f64(k)
+ stat := f64(0)
+ for c in counts {
+ d := f64(c) - exp
+ stat += (d * d) / exp
+ }
+ return stat
+}
+
+// Helper: check bit balance on u64 across many samples
+@(private = "file")
+expect_u64_bit_balance :: proc(t: ^testing.T, rng: Generator, samples: int, sigma_k: f64 = 6) {
+ rand.reset(t.seed, rng.gen)
+
+ ones: [64]int
+ for i := 0; i < samples; i += 1 {
+ v := rand.uint64(rng.gen)
+ for b := 0; b < 64; b += 1 {
+ ones[b] += int((v >> u64(b)) & 1)
+ }
+ }
+ mu := f64(samples) * 0.5
+ sigma := math.sqrt(f64(samples) * 0.25)
+ limit := sigma_k * sigma
+ for b := 0; b < 64; b += 1 {
+ diff := math.abs(f64(ones[b]) - mu)
+ if diff > limit {
+ testing.expectf(t, false, "rng '%s': u64 bit %d imbalance: ones=%d samples=%d diff=%.1f limit=%.1f", rng.name, b, ones[b], samples, diff, limit)
+ return
+ }
+ }
+}
+
+// Helper: Uniformity sanity via 4D sign orthant chi-square with modest sample size.
+@(private = "file")
+expect_quaternion_sign_uniformity :: proc(t: ^testing.T, rng: Generator, iterations: int) {
+ counts: [16]int
+ for _ in 0..<iterations {
+ // Map 4D signs to 0..15 index
+ x := rand.float64_range(-10, 10, rng.gen)
+ y := rand.float64_range(-10, 10, rng.gen)
+ z := rand.float64_range(-10, 10, rng.gen)
+ w := rand.float64_range(-10, 10, rng.gen)
+ idx := 0
+ if x >= 0 { idx |= 1 }
+ if y >= 0 { idx |= 2 }
+ if z >= 0 { idx |= 4 }
+ if w >= 0 { idx |= 8 }
+ counts[idx] += 1
+ }
+ // df = 15. For a modest sample size, use a generous cutoff to reduce flakiness.
+ // Chi-square critical values (df=15): p=0.001 -> ~37.7, p=0.0001 -> ~43.8
+ // We accept < 55 as a conservative stability bound across platforms.
+ chi := chi_square_equal(counts[:])
+ testing.expectf(t, chi < 55.0, "rng '%s': 4D sign chi-square too high: %.3f (counts=%v)", rng.name, chi, counts)
+}
diff --git a/tests/internal/test_chacha8rand.odin b/tests/internal/test_chacha8rand.odin
new file mode 100644
index 000000000..378b398f0
--- /dev/null
+++ b/tests/internal/test_chacha8rand.odin
@@ -0,0 +1,151 @@
+package test_internal
+
+import "base:runtime"
+import "core:bytes"
+import "core:encoding/endian"
+import "core:math/rand"
+import "core:testing"
+
+@(private = "file")
+ITERS :: 10000000
+@(private = "file")
+ITERS_BULK :: 1000
+
+@(private = "file")
+SAMPLE_SEED : string : "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456"
+@(private = "file")
+SAMPLE_OUTPUT := []u64{
+ 0xb773b6063d4616a5, 0x1160af22a66abc3c, 0x8c2599d9418d287c, 0x7ee07e037edc5cd6,
+ 0xcfaa9ee02d1c16ad, 0x0e090eef8febea79, 0x3c82d271128b5b3e, 0x9c5addc11252a34f,
+ 0xdf79bb617d6ceea6, 0x36d553591f9d736a, 0xeef0d14e181ee01f, 0x089bfc760ae58436,
+ 0xd9e52b59cc2ad268, 0xeb2fb4444b1b8aba, 0x4f95c8a692c46661, 0xc3c6323217cae62c,
+ 0x91ebb4367f4e2e7e, 0x784cf2c6a0ec9bc6, 0x5c34ec5c34eabe20, 0x4f0a8f515570daa8,
+ 0xfc35dcb4113d6bf2, 0x5b0da44c645554bc, 0x6d963da3db21d9e1, 0xeeaefc3150e500f3,
+ 0x2d37923dda3750a5, 0x380d7a626d4bc8b0, 0xeeaf68ede3d7ee49, 0xf4356695883b717c,
+ 0x846a9021392495a4, 0x8e8510549630a61b, 0x18dc02545dbae493, 0x0f8f9ff0a65a3d43,
+ 0xccf065f7190ff080, 0xfd76d1aa39673330, 0x95d232936cba6433, 0x6c7456d1070cbd17,
+ 0x462acfdaff8c6562, 0x5bafab866d34fc6a, 0x0c862f78030a2988, 0xd39a83e407c3163d,
+ 0xc00a2b7b45f22ebf, 0x564307c62466b1a9, 0x257e0424b0c072d4, 0x6fb55e99496c28fe,
+ 0xae9873a88f5cd4e0, 0x4657362ac60d3773, 0x1c83f91ecdf23e8e, 0x6fdc0792c15387c0,
+ 0x36dad2a30dfd2b5c, 0xa4b593290595bdb7, 0x4de18934e4cc02c5, 0xcdc0d604f015e3a7,
+ 0xfba0dbf69ad80321, 0x60e8bea3d139de87, 0xd18a4d851ef48756, 0x6366447c2215f34a,
+ 0x05682e97d3d007ee, 0x4c0e8978c6d54ab2, 0xcf1e9f6a6712edc2, 0x061439414c80cfd3,
+ 0xd1a8b6e2745c0ead, 0x31a7918d45c410e8, 0xabcc61ad90216eec, 0x4040d92d2032a71a,
+ 0x3cd2f66ffb40cd68, 0xdcd051c07295857a, 0xeab55cbcd9ab527e, 0x18471dce781bdaac,
+ 0xf7f08cd144dc7252, 0x5804e0b13d7f40d1, 0x5cb1a446e4b2d35b, 0xe6d4a728d2138a06,
+ 0x05223e40ca60dad8, 0x2d61ec3206ac6a68, 0xab692356874c17b8, 0xc30954417676de1c,
+ 0x4f1ace3732225624, 0xfba9510813988338, 0x997f200f52752e11, 0x1116aaafe86221fa,
+ 0x07ce3b5cb2a13519, 0x2956bc72bc458314, 0x4188b7926140eb78, 0x56ca6dbfd4adea4d,
+ 0x7fe3c22349340ce5, 0x35c08f9c37675f8a, 0x11e1c7fbef5ed521, 0x98adc8464ec1bc75,
+ 0xd163b2c73d1203f8, 0x8c761ee043a2f3f3, 0x24b99d6accecd7b7, 0x793e31aa112f0370,
+ 0x8e87dc2a19285139, 0x4247ae04f7096e25, 0x514f3122926fe20f, 0xdc6fb3f045d2a7e9,
+ 0x15cb30cecdd18eba, 0xcbc7fdecf6900274, 0x3fb5c696dc8ba021, 0xd1664417c8d274e6,
+ 0x05f7e445ea457278, 0xf920bbca1b9db657, 0x0c1950b4da22cb99, 0xf875baf1af09e292,
+ 0xbed3d7b84250f838, 0xf198e8080fd74160, 0xc9eda51d9b7ea703, 0xf709ef55439bf8f6,
+ 0xd20c74feebf116fc, 0x305668eb146d7546, 0x829af3ec10d89787, 0x15b8f9697b551dbc,
+ 0xfc823c6c8e64b8c9, 0x345585e8183b40bc, 0x674b4171d6581368, 0x1234d81cd670e9f7,
+ 0x0e505210d8a55e19, 0xe8258d69eeeca0dc, 0x05d4c452e8baf67e, 0xe8dbe30116a45599,
+ 0x1cf08ce1b1176f00, 0xccf7d0a4b81ecb49, 0x303fea136b2c430e, 0x861d6c139c06c871,
+ 0x5f41df72e05e0487, 0x25bd7e1e1ae26b1d, 0xbe9f4004d662a41d, 0x65bf58d483188546,
+ 0xd1b27cff69db13cc, 0x01a6663372c1bb36, 0x578dd7577b727f4d, 0x19c78f066c083cf6,
+ 0xdbe014d4f9c391bb, 0x97fbb2dd1d13ffb3, 0x31c91e0af9ef8d4f, 0x094dfc98402a43ba,
+ 0x069bd61bea37b752, 0x5b72d762e8d986ca, 0x72ee31865904bc85, 0xd1f5fdc5cd36c33e,
+ 0xba9b4980a8947cad, 0xece8f05eac49ab43, 0x65fe1184abae38e7, 0x2d7cb9dea5d31452,
+ 0xcc71489476e467e3, 0x4c03a258a578c68c, 0x00efdf9ecb0fd8fc, 0x9924cad471e2666d,
+ 0x87f8668318f765e9, 0xcb4dc57c1b55f5d8, 0xd373835a86604859, 0xe526568b5540e482,
+ 0x1f39040f08586fec, 0xb764f3f00293f8e6, 0x049443a2f6bd50a8, 0x76fec88697d3941a,
+ 0x3efb70d039bae7a2, 0xe2f4611368eca8a8, 0x7c007a96e01d2425, 0xbbcce5768e69c5bf,
+ 0x784fb4985c42aac3, 0xf72b5091aa223874, 0x3630333fb1e62e07, 0x8e7319ebdebbb8de,
+ 0x2a3982bca959fa00, 0xb2b98b9f964ba9b3, 0xf7e31014adb71951, 0xebd0fca3703acc82,
+ 0xec654e2a2fe6419a, 0xb326132d55a52e2c, 0x2248c57f44502978, 0x32710c2f342daf16,
+ 0x0517b47b5acb2bec, 0x4c7a718fca270937, 0xd69142bed0bcc541, 0xe40ebcb8ff52ce88,
+ 0x3e44a2dbc9f828d4, 0xc74c2f4f8f873f58, 0x3dbf648eb799e45b, 0x33f22475ee0e86f8,
+ 0x1eb4f9ee16d47f65, 0x40f8d2b8712744e3, 0xb886b4da3cb14572, 0x2086326fbdd6f64d,
+ 0xcc3de5907dd882b9, 0xa2e8b49a5ee909df, 0xdbfb8e7823964c10, 0x70dd6089ef0df8d5,
+ 0x30141663cdd9c99f, 0x04b805325c240365, 0x7483d80314ac12d6, 0x2b271cb91aa7f5f9,
+ 0x97e2245362abddf0, 0x5a84f614232a9fab, 0xf71125fcda4b7fa2, 0x1ca5a61d74b27267,
+ 0x38cc6a9b3adbcb45, 0xdde1bb85dc653e39, 0xe9d0c8fa64f89fd4, 0x02c5fb1ecd2b4188,
+ 0xf2bd137bca5756e5, 0xadefe25d121be155, 0x56cd1c3c5d893a8e, 0x4c50d337beb65bb9,
+ 0x918c5151675cf567, 0xaba649ffcfb56a1e, 0x20c74ab26a2247cd, 0x71166bac853c08da,
+ 0xb07befe2e584fc5d, 0xda45ff2a588dbf32, 0xdb98b03c4d75095e, 0x60285ae1aaa65a4c,
+ 0xf93b686a263140b8, 0xde469752ee1c180e, 0xcec232dc04129aae, 0xeb916baa1835ea04,
+ 0xd49c21c8b64388ff, 0x72a82d9658864888, 0x003348ef7eac66a8, 0x7f6f67e655b209eb,
+ 0x532ffb0b7a941b25, 0xd940ade6128deede, 0xdf24f2a1af89fe23, 0x95aa3b4988195ae0,
+ 0x3da649404f94be4a, 0x692dad132c3f7e27, 0x40aee76ecaaa9eb8, 0x1294a01e09655024,
+ 0x6df797abdba4e4f5, 0xea2fb6024c1d7032, 0x5f4e0492295489fc, 0x57972914ea22e06a,
+ 0x9a8137d133aad473, 0xa2e6dd6ae7cdf2f3, 0x9f42644f18086647, 0x16d03301c170bd3e,
+ 0x908c416fa546656d, 0xe081503be22e123e, 0x077cf09116c4cc72, 0xcbd25cd264b7f229,
+ 0x3db2f468ec594031, 0x46c00e734c9badd5, 0xd0ec0ac72075d861, 0x3037cb3cf80b7630,
+ 0x574c3d7b3a2721c6, 0xae99906a0076824b, 0xb175a5418b532e70, 0xd8b3e251ee231ddd,
+ 0xb433eec25dca1966, 0x530f30dc5cff9a93, 0x9ff03d98b53cd335, 0xafc4225076558cdf,
+ 0xef81d3a28284402a, 0x110bdbf51c110a28, 0x9ae1b255d027e8f6, 0x7de3e0aa24688332,
+ 0xe483c3ecd2067ee2, 0xf829328b276137e6, 0xa413ccad57562cad, 0xe6118e8b496acb1f,
+ 0x8288dca6da5ec01f, 0xa53777dc88c17255, 0x8a00f1e0d5716eda, 0x618e6f47b7a720a8,
+ 0x9e3907b0c692a841, 0x978b42ca963f34f3, 0x75e4b0cd98a7d7ef, 0xde4dbd6e0b5f4752,
+ 0x0252e4153f34493f, 0x50f0e7d803734ef9, 0x237766a38ed167ee, 0x4124414001ee39a0,
+ 0xd08df643e535bb21, 0x34f575b5a9a80b74, 0x2c343af87297f755, 0xcd8b6d99d821f7cb,
+ 0xe376fd7256fc48ae, 0xe1b06e7334352885, 0xfa87b26f86c169eb, 0x36c1604665a971de,
+ 0xdba147c2239c8e80, 0x6b208e69fc7f0e24, 0x8795395b6f2b60c3, 0x05dabee9194907f4,
+ 0xb98175142f5ed902, 0x5e1701e2021ddc81, 0x0875aba2755eed08, 0x778d83289251de95,
+ 0x3bfbe46a039ecb31, 0xb24704fce4cbd7f9, 0x6985ffe9a7c91e3d, 0xc8efb13df249dabb,
+ 0xb1037e64b0f4c9f6, 0x55f69fd197d6b7c3, 0x672589d71d68a90c, 0xbebdb8224f50a77e,
+ 0x3f589f80007374a7, 0xd307f4635954182a, 0xcff5850c10d4fd90, 0xc6da02dfb6408e15,
+ 0x93daeef1e2b1a485, 0x65d833208aeea625, 0xe2b13fa13ed3b5fa, 0x67053538130fb68e,
+ 0xc1042f6598218fa9, 0xee5badca749b8a2e, 0x6d22a3f947dae37d, 0xb62c6d1657f4dbaf,
+ 0x6e007de69704c20b, 0x1af2b913fc3841d8, 0xdc0e47348e2e8e22, 0x9b1ddef1cf958b22,
+ 0x632ed6b0233066b8, 0xddd02d3311bed8f2, 0xf147cfe1834656e9, 0x399aaa49d511597a,
+ 0x6b14886979ec0309, 0x64fc4ac36b5afb97, 0xb82f78e07f7cf081, 0x10925c9a323d0e1b,
+ 0xf451c79ee13c63f6, 0x7c2fc180317876c7, 0x35a12bd9eecb7d22, 0x335654a539621f90,
+ 0xcc32a3f35db581f0, 0xc60748a80b2369cb, 0x7c4dd3b08591156b, 0xac1ced4b6de22291,
+ 0xa32cfa2df134def5, 0x627108918dea2a53, 0x0555b1608fcb4ff4, 0x143ee7ac43aaa33c,
+ 0xdae90ce7cf4fc218, 0x4d68fc2582bcf4b5, 0x37094e1849135d71, 0xf7857e09f3d49fd8,
+ 0x007538c503768be7, 0xedf648ba2f6be601, 0xaa347664dd72513e, 0xbe63893c6ef23b86,
+ 0x130b85710605af97, 0xdd765c6b1ef6ab56, 0xf3249a629a97dc6b, 0x2a114f9020fab8e5,
+ 0x5a69e027cfc6ad08, 0x3c4ccb36f1a5e050, 0x2e9e7d596834f0a5, 0x2430be6858fce789,
+ 0xe90b862f2466e597, 0x895e2884f159a9ec, 0x26ab8fa4902fcb57, 0xa6efff5c54e1fa50,
+ 0x333ac4e5811a8255, 0xa58d515f02498611, 0xfe5a09dcb25c6ef4, 0x03898988ab5f5818,
+ 0x289ff6242af6c617, 0x3d9dd59fd381ea23, 0x52d7d93d8a8aae51, 0xc76a123d511f786f,
+ 0xf68901edaf00c46c, 0x8c630871b590de80, 0x05209c308991e091, 0x1f809f99b4788177,
+ 0x11170c2eb6c19fd8, 0x44433c779062ba58, 0xc0acb51af1874c45, 0x9f2e134284809fa1,
+ 0xedb523bd15c619fa, 0x02d97fd53ecc23c0, 0xacaf05a34462374c, 0xddd9c6d34bffa11f,
+}
+
+@(test)
+chacha8rand_u64s :: proc(t: ^testing.T) {
+ st: runtime.Default_Random_State
+ context.random_generator = runtime.default_random_generator(&st)
+ rand.reset_bytes(transmute([]byte)(SAMPLE_SEED))
+
+ for expected, i in SAMPLE_OUTPUT {
+ actual := rand.uint64()
+ testing.expectf(t, expected == actual, "[%d]: got %x (expected %x)", i, actual, expected)
+ }
+}
+
+@(test)
+chacha8rand_bytes :: proc(t: ^testing.T) {
+ st: runtime.Default_Random_State
+ context.random_generator = runtime.default_random_generator(&st)
+ rand.reset_bytes(transmute([]byte)(SAMPLE_SEED))
+
+ // Test a massive bulk read.
+ buf := make([]byte, len(SAMPLE_OUTPUT) * size_of(u64), context.temp_allocator)
+ n := rand.read(buf)
+ testing.expectf(t, n == len(buf), "insufficient output: got %d (expected %d)", n, len(buf))
+
+ for expected, i in SAMPLE_OUTPUT {
+ actual, _ := endian.get_u64(buf[i*8:], .Little)
+ testing.expectf(t, expected == actual, "[%d]: got %x (expected %x)", i, actual, expected)
+ }
+
+ // Test that the internal state always advances by a multiple of
+ // 8-bytes.
+ rand.reset_bytes(transmute([]byte)(SAMPLE_SEED))
+ tmp: [8]byte
+ off: int
+ for i := 1; i < 8; i += 1 {
+ _ = rand.read(tmp[:i])
+ testing.expect(t, bytes.equal(tmp[:i], buf[off:off+i]))
+ off += 8
+ }
+}