diff options
Diffstat (limited to 'internal/casino/hardening_casino_rng_test.go')
| -rw-r--r-- | internal/casino/hardening_casino_rng_test.go | 75 |
1 files changed, 75 insertions, 0 deletions
diff --git a/internal/casino/hardening_casino_rng_test.go b/internal/casino/hardening_casino_rng_test.go new file mode 100644 index 0000000..2d44441 --- /dev/null +++ b/internal/casino/hardening_casino_rng_test.go @@ -0,0 +1,75 @@ +package casino + +import "testing" + +// RNG seams: outcomes must not be replayable from a clock, while tests keep +// a deterministic, injectable seed. + +func TestNewManagerSeedIsADeterministicSeam(t *testing.T) { + cfg := MachineConfig{ID: "slots", Game: GameSlots} + same := []*Machine{ + NewManager(7).EnsureMachine(1, cfg), + NewManager(7).EnsureMachine(1, cfg), + } + other := NewManager(8).EnsureMachine(1, cfg) + + first := machineDraws(same[0]) + if got := machineDraws(same[1]); !sameDraws(first, got) { + t.Fatalf("the same seed produced different slot streams: %v vs %v", first, got) + } + if got := machineDraws(other); sameDraws(first, got) { + t.Fatalf("different seeds produced the same slot stream: %v", got) + } +} + +func TestNewManagerEntropySeedsFromCryptoRand(t *testing.T) { + if seed := entropySeed(); seed < 0 { + t.Fatalf("entropy seed %d is negative", seed) + } + seen := make(map[int64]bool) + for i := 0; i < 4; i++ { + m := NewManagerEntropy() + if m.seed < 0 { + t.Fatalf("NewManagerEntropy seed %d is negative", m.seed) + } + seen[m.seed] = true + } + if len(seen) < 2 { + t.Fatalf("NewManagerEntropy reused one seed across %d managers", len(seen)) + } +} + +// Each game gets its own stream seeded from the manager's: no two tables or +// machines may share reel results. +func TestManagerGamesKeepIndependentStreams(t *testing.T) { + m := NewManager(3) + first := m.EnsureMachine(1, MachineConfig{ID: "slots_a", Game: GameSlots}) + second := m.EnsureMachine(1, MachineConfig{ID: "slots_b", Game: GameSlots}) + if first == nil || second == nil { + t.Fatal("machines were not created") + } + if sameDraws(machineDraws(first), machineDraws(second)) { + t.Fatal("two machines share one RNG stream") + } +} + +// machineDraws consumes n values from a game's own stream. +func machineDraws(m *Machine) []int64 { + draws := make([]int64, 8) + for i := range draws { + draws[i] = m.rng.Int63() + } + return draws +} + +func sameDraws(a, b []int64) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} |
