dice_test.gno
3.01 Kb · 123 lines
1package dice
2
3import (
4 "strings"
5 "testing"
6
7 "gno.land/p/nt/avl/v0"
8)
9
10// resetState clears package globals between tests since realm state is global.
11func resetState() {
12 count = [6]int{}
13 total = 0
14 players = avl.NewTree()
15}
16
17func TestDeriveFaceRange(t *testing.T) {
18 // Every derived face must land in [1,6] regardless of entropy inputs.
19 cases := []struct {
20 height int64
21 addr string
22 n int
23 }{
24 {0, "g1abc", 0},
25 {1, "g1abc", 1},
26 {999999, "g1xyzxyzxyz", 42},
27 {-1, "", 0},
28 {123456789, "g1deadbeef", 7},
29 }
30 for _, c := range cases {
31 f := deriveFace(c.height, c.addr, c.n)
32 if f < 1 || f > 6 {
33 t.Errorf("deriveFace(%d,%q,%d) = %d, want 1..6", c.height, c.addr, c.n, f)
34 }
35 }
36}
37
38func TestDeriveFaceDeterministic(t *testing.T) {
39 // Same inputs -> same output (provably fair / reproducible).
40 a := deriveFace(100, "g1caller", 3)
41 b := deriveFace(100, "g1caller", 3)
42 if a != b {
43 t.Errorf("deriveFace not deterministic: %d != %d", a, b)
44 }
45 // Different roll count generally changes entropy; at least it must not panic
46 // and must stay in range.
47 c := deriveFace(100, "g1caller", 4)
48 if c < 1 || c > 6 {
49 t.Errorf("deriveFace out of range: %d", c)
50 }
51}
52
53func TestDeriveFaceVariesByCaller(t *testing.T) {
54 // Two different addresses at the same height should not be forced equal;
55 // verify the hash actually mixes the address by finding at least one
56 // differing pair across a small sweep.
57 diff := false
58 for i := 0; i < 10; i++ {
59 if deriveFace(50, "g1alice", i) != deriveFace(50, "g1bob", i) {
60 diff = true
61 break
62 }
63 }
64 if !diff {
65 t.Error("deriveFace never differs between two distinct callers over 10 rolls")
66 }
67}
68
69func TestRenderEmpty(t *testing.T) {
70 resetState()
71 out := Render("")
72 if !strings.Contains(out, "Provably-Fair Dice") {
73 t.Error("Render missing title")
74 }
75 if !strings.Contains(out, "Total rolls:** 0") {
76 t.Errorf("Render should report 0 total rolls, got:\n%s", out)
77 }
78 if !strings.Contains(out, "No rolls yet") {
79 t.Error("Render should show empty-state message")
80 }
81}
82
83func TestDistributionAccounting(t *testing.T) {
84 resetState()
85 // Simulate the state mutations Roll performs, without a realm frame.
86 faces := []int{1, 1, 3, 6, 6, 6}
87 for _, f := range faces {
88 count[f-1]++
89 total++
90 }
91 if total != len(faces) {
92 t.Errorf("total = %d, want %d", total, len(faces))
93 }
94 d := Distribution()
95 if d[0] != 2 {
96 t.Errorf("face 1 count = %d, want 2", d[0])
97 }
98 if d[5] != 3 {
99 t.Errorf("face 6 count = %d, want 3", d[5])
100 }
101 if d[1] != 0 {
102 t.Errorf("face 2 count = %d, want 0", d[1])
103 }
104 out := Render("")
105 if !strings.Contains(out, "Total rolls:** 6") {
106 t.Errorf("Render should report 6 total rolls, got:\n%s", out)
107 }
108}
109
110func TestBar(t *testing.T) {
111 full := bar(10, 10, 10)
112 if strings.Count(full, "▓") != 10 {
113 t.Errorf("full bar want 10 filled, got %q", full)
114 }
115 empty := bar(0, 10, 10)
116 if strings.Count(empty, "░") != 10 {
117 t.Errorf("empty bar want 10 unfilled, got %q", empty)
118 }
119 half := bar(5, 10, 10)
120 if strings.Count(half, "▓") != 5 {
121 t.Errorf("half bar want 5 filled, got %q", half)
122 }
123}