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