74 lines
3.3 KiB
FSharp
74 lines
3.3 KiB
FSharp
module Snake.Tests.M04Random
|
|
|
|
open Expecto
|
|
open Snake
|
|
open Snake.Tests.Fixtures
|
|
|
|
let private pair (f: System.Text.Json.JsonElement -> 'a) (v: System.Text.Json.JsonElement) : 'a * uint32 =
|
|
(f (v.Item 0), u32 (v.Item 1))
|
|
|
|
// n draws of `intBelow n` in a row, each starting from the seed the previous one left behind.
|
|
let rec private draws (n: int) (seed: uint32) (count: int) : int list =
|
|
if count = 0 then
|
|
[]
|
|
else
|
|
let v, s = Random.run seed (Random.intBelow n)
|
|
v :: draws n s (count - 1)
|
|
|
|
[<Tests>]
|
|
let tests =
|
|
testList "M04 Random" [
|
|
// Same values as the JS and Scala tracks: if mulberry32 drifts (signed math, overflow), these fail.
|
|
yield! fixtureTests "m04-random" "nextUint32" (fun c ->
|
|
Expect.equal (Random.run (u32 (arg 0 c)) Random.nextUint32) (pair u32 (expected c)) "nextUint32")
|
|
|
|
yield! fixtureTests "m04-int-below" "intBelow" (fun c ->
|
|
Expect.equal (Random.run (u32 (arg 1 c)) (Random.intBelow (num (arg 0 c)))) (pair num (expected c)) "intBelow")
|
|
|
|
testCase "the recorded steps chain: each step starts from the previous step's seed" (fun () ->
|
|
let cs = cases "m04-random"
|
|
|
|
for i, c in List.indexed cs do
|
|
if not ((name c).EndsWith("-step-1")) then
|
|
let previous = expected cs[i - 1]
|
|
Expect.equal (u32 (arg 0 c)) (u32 previous[1]) (name c))
|
|
|
|
testCase "pure returns its value and leaves the seed alone" (fun () ->
|
|
Expect.equal (Random.run 99u (Random.pure "x")) ("x", 99u) "pure")
|
|
|
|
testCase "map applies f to the value and keeps the seed from the underlying step" (fun () ->
|
|
let v, s = Random.run 5u Random.nextUint32
|
|
Expect.equal (Random.run 5u (Random.map (fun x -> x + 1u) Random.nextUint32)) (v + 1u, s) "map")
|
|
|
|
testCase "map with the identity function changes nothing" (fun () ->
|
|
Expect.equal (Random.run 5u (Random.map id Random.nextUint32)) (Random.run 5u Random.nextUint32) "map id")
|
|
|
|
testCase "bind threads the seed through both steps" (fun () ->
|
|
let a, s1 = Random.run 5u Random.nextUint32
|
|
let b, s2 = Random.run s1 Random.nextUint32
|
|
|
|
let both =
|
|
Random.nextUint32 |> Random.bind (fun x -> Random.nextUint32 |> Random.map (fun y -> (x, y)))
|
|
|
|
Expect.equal (Random.run 5u both) ((a, b), s2) "bind")
|
|
|
|
testCase "bind with pure on the left or right changes nothing" (fun () ->
|
|
Expect.equal
|
|
(Random.run 5u (Random.pure 3u |> Random.bind (fun x -> Random.map (fun v -> v + x) Random.nextUint32)))
|
|
(Random.run 5u (Random.map (fun v -> v + 3u) Random.nextUint32))
|
|
"left identity"
|
|
|
|
Expect.equal
|
|
(Random.run 5u (Random.bind Random.pure Random.nextUint32))
|
|
(Random.run 5u Random.nextUint32)
|
|
"right identity")
|
|
|
|
testCase "intBelow always lands in [0, n)" (fun () ->
|
|
for n in [ 1; 2; 7; 400 ] do
|
|
for v in draws n 123u 50 do
|
|
Expect.isTrue (v >= 0 && v < n) $"{v} not in [0, {n})")
|
|
|
|
testCase "running a Random does not mutate anything: same seed, same answer" (fun () ->
|
|
Expect.equal (Random.run 8u Random.nextUint32) (Random.run 8u Random.nextUint32) "same answer")
|
|
]
|