Files

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")
]