Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

closestguess_test.gno

4.13 Kb · 151 lines
  1package closestguess
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"gno.land/p/nt/testutils/v0"
  8)
  9
 10// resetGame re-initializes package globals so each test starts clean; init()
 11// runs only once for the whole test binary.
 12func resetGame(height int64) {
 13	current = newRound(1, height)
 14	entries = nil
 15	leaderboard = nil
 16}
 17
 18func TestTargetFromHeight(t *testing.T) {
 19	tests := []struct {
 20		name   string
 21		height int64
 22	}{
 23		{"zero", 0},
 24		{"one", 1},
 25		{"large", 123456},
 26		{"negative", -42},
 27	}
 28	for _, tt := range tests {
 29		got := targetFromHeight(tt.height, 1)
 30		if got < minTarget || got > maxTarget {
 31			t.Errorf("%s: targetFromHeight(%d,1)=%d, want in %d..%d", tt.name, tt.height, got, minTarget, maxTarget)
 32		}
 33		if again := targetFromHeight(tt.height, 1); again != got {
 34			t.Errorf("%s: non-deterministic: %d != %d", tt.name, again, got)
 35		}
 36	}
 37
 38	// Different round numbers at the same height should (almost always) mix
 39	// to different targets, so consecutive rounds don't repeat.
 40	a := targetFromHeight(100, 1)
 41	b := targetFromHeight(100, 2)
 42	if a == b {
 43		t.Errorf("targetFromHeight(100,1) == targetFromHeight(100,2) == %d, want them to differ", a)
 44	}
 45}
 46
 47func TestDistance(t *testing.T) {
 48	cases := []struct{ a, b, want int }{
 49		{5, 5, 0},
 50		{5, 8, 3},
 51		{8, 5, 3},
 52		{0, 1000, 1000},
 53	}
 54	for _, c := range cases {
 55		if got := distance(c.a, c.b); got != c.want {
 56			t.Errorf("distance(%d,%d) = %d, want %d", c.a, c.b, got, c.want)
 57		}
 58	}
 59}
 60
 61// TestGuessRejectsOutOfRangeAndDuplicate exercises the crossing Guess
 62// function, so it takes cur realm and calls through cross(cur). A panic
 63// raised across the crossing call boundary isn't catchable by a plain
 64// defer/recover; revive() is the boundary-aware way to assert on it.
 65func TestGuessRejectsOutOfRangeAndDuplicate(cur realm, t *testing.T) {
 66	resetGame(10)
 67
 68	alice := testutils.TestAddress("alice")
 69	testing.SetRealm(testing.NewUserRealm(alice))
 70	if r := revive(func() { Guess(cross(cur), 0) }); r == nil {
 71		t.Error("Guess(0) should panic: out of range")
 72	}
 73
 74	testing.SetRealm(testing.NewUserRealm(alice))
 75	msg := Guess(cross(cur), 500)
 76	if !strings.Contains(msg, "recorded") {
 77		t.Errorf("Guess(500) = %q, want it to mention 'recorded'", msg)
 78	}
 79
 80	testing.SetRealm(testing.NewUserRealm(alice))
 81	if r := revive(func() { Guess(cross(cur), 501) }); r == nil {
 82		t.Error("second Guess from the same address should panic")
 83	}
 84}
 85
 86// TestRevealPicksClosestAndStartsNewRound exercises Guess + Reveal together.
 87func TestRevealPicksClosestAndStartsNewRound(cur realm, t *testing.T) {
 88	resetGame(50)
 89	target := current.target
 90	prevRound := current.number
 91
 92	alice := testutils.TestAddress("alice")
 93	bob := testutils.TestAddress("bob")
 94
 95	far := target + 100
 96	if far > maxTarget {
 97		far = target - 100
 98	}
 99	if far < minTarget {
100		far = minTarget
101	}
102	near := target + 1
103	if near > maxTarget {
104		near = target - 1
105	}
106	if near < minTarget {
107		near = minTarget
108	}
109
110	testing.SetRealm(testing.NewUserRealm(alice))
111	Guess(cross(cur), far)
112	testing.SetRealm(testing.NewUserRealm(bob))
113	Guess(cross(cur), near)
114
115	msg := Reveal(cross(cur))
116	if !strings.Contains(msg, "revealed") {
117		t.Errorf("Reveal() = %q, want it to mention 'revealed'", msg)
118	}
119	if len(leaderboard) != 1 {
120		t.Fatalf("leaderboard len = %d, want 1", len(leaderboard))
121	}
122	if leaderboard[0].winner != bob.String() {
123		t.Errorf("winner = %q, want %q (closer guess)", leaderboard[0].winner, bob.String())
124	}
125	if current.number != prevRound+1 {
126		t.Errorf("round number = %d, want %d", current.number, prevRound+1)
127	}
128	if len(entries) != 0 {
129		t.Errorf("entries should reset after Reveal, got %d", len(entries))
130	}
131}
132
133// TestRevealPanicsWithNoGuesses checks the panic path via revive() (see the
134// note on TestGuessRejectsOutOfRangeAndDuplicate).
135func TestRevealPanicsWithNoGuesses(cur realm, t *testing.T) {
136	resetGame(1)
137	if r := revive(func() { Reveal(cross(cur)) }); r == nil {
138		t.Error("Reveal() with no guesses should panic")
139	}
140}
141
142// TestRenderContainsState is a plain read — no cur.
143func TestRenderContainsState(t *testing.T) {
144	resetGame(3)
145	out := Render("")
146	for _, want := range []string{"Closest Guess", "Current round", "Leaderboard"} {
147		if !strings.Contains(out, want) {
148			t.Errorf("Render() missing %q", want)
149		}
150	}
151}