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.

ballot_test.gno

5.88 Kb · 217 lines
  1package ballot
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"gno.land/p/nt/avl/v0"
  8	"gno.land/p/nt/testutils/v0"
  9)
 10
 11func resetState() {
 12	var zero address
 13	chairperson = zero
 14	initialized = false
 15	proposals = nil
 16	voters = avl.NewTree()
 17}
 18
 19// Crossing: Init/Vote/Delegate/GiveRightToVote mutate realm state, so tests
 20// that call them need `cur realm`.
 21func TestInitSetsChairpersonAndProposals(cur realm, t *testing.T) {
 22	resetState()
 23	alice := testutils.TestAddress("alice")
 24
 25	testing.SetRealm(testing.NewUserRealm(alice))
 26	Init(cross(cur), "Alice, Bob , Carol")
 27
 28	if chairperson != alice {
 29		t.Fatal("chairperson should be the Init caller")
 30	}
 31	if len(proposals) != 3 {
 32		t.Fatalf("len(proposals) = %d, want 3", len(proposals))
 33	}
 34	if proposals[0].name != "Alice" || proposals[1].name != "Bob" || proposals[2].name != "Carol" {
 35		t.Fatal("proposal names should be trimmed and in order")
 36	}
 37
 38	rec := revive(func() { Init(cross(cur), "X") })
 39	if rec == nil {
 40		t.Fatal("expected panic on double Init")
 41	}
 42}
 43
 44func TestGiveRightToVoteOnlyChairperson(cur realm, t *testing.T) {
 45	resetState()
 46	alice := testutils.TestAddress("alice")
 47	bob := testutils.TestAddress("bob")
 48	mallory := testutils.TestAddress("mallory")
 49
 50	testing.SetRealm(testing.NewUserRealm(alice))
 51	Init(cross(cur), "A,B")
 52
 53	testing.SetRealm(testing.NewUserRealm(mallory))
 54	rec := revive(func() { GiveRightToVote(cross(cur), bob.String()) })
 55	if rec == nil {
 56		t.Fatal("expected panic when a non-chairperson grants voting rights")
 57	}
 58
 59	testing.SetRealm(testing.NewUserRealm(alice))
 60	GiveRightToVote(cross(cur), bob.String())
 61
 62	v := voters.Get(bob.String())
 63	if v == nil || v.(*voterInfo).weight != 1 {
 64		t.Fatal("bob should now have voting weight 1")
 65	}
 66}
 67
 68func TestVoteDirectly(cur realm, t *testing.T) {
 69	resetState()
 70	alice := testutils.TestAddress("alice")
 71
 72	testing.SetRealm(testing.NewUserRealm(alice))
 73	Init(cross(cur), "A,B")
 74	Vote(cross(cur), 1)
 75
 76	if proposals[1].votes != 1 {
 77		t.Fatalf("proposals[1].votes = %d, want 1", proposals[1].votes)
 78	}
 79	if WinnerName() != "B" {
 80		t.Fatalf("WinnerName() = %q, want B", WinnerName())
 81	}
 82
 83	rec := revive(func() { Vote(cross(cur), 0) })
 84	if rec == nil {
 85		t.Fatal("expected panic on double vote")
 86	}
 87}
 88
 89func TestDelegateToUnvotedAccumulatesWeight(cur realm, t *testing.T) {
 90	resetState()
 91	alice := testutils.TestAddress("alice")
 92	bob := testutils.TestAddress("bob")
 93
 94	testing.SetRealm(testing.NewUserRealm(alice))
 95	Init(cross(cur), "A,B")
 96	GiveRightToVote(cross(cur), bob.String())
 97
 98	testing.SetRealm(testing.NewUserRealm(bob))
 99	Delegate(cross(cur), alice.String())
100
101	aliceVoter := voters.Get(alice.String())
102	if aliceVoter.(*voterInfo).weight != 2 {
103		t.Fatalf("alice.weight = %d, want 2", aliceVoter.(*voterInfo).weight)
104	}
105
106	testing.SetRealm(testing.NewUserRealm(alice))
107	Vote(cross(cur), 0)
108
109	if proposals[0].votes != 2 {
110		t.Fatalf("proposals[0].votes = %d, want 2 (alice's own weight + bob's delegation)", proposals[0].votes)
111	}
112}
113
114func TestDelegateToAlreadyVotedAddsDirectly(cur realm, t *testing.T) {
115	resetState()
116	alice := testutils.TestAddress("alice")
117	bob := testutils.TestAddress("bob")
118
119	testing.SetRealm(testing.NewUserRealm(alice))
120	Init(cross(cur), "A,B")
121	GiveRightToVote(cross(cur), bob.String())
122	Vote(cross(cur), 1)
123
124	testing.SetRealm(testing.NewUserRealm(bob))
125	Delegate(cross(cur), alice.String())
126
127	if proposals[1].votes != 2 {
128		t.Fatalf("proposals[1].votes = %d, want 2 (alice's vote + bob's delegated weight)", proposals[1].votes)
129	}
130}
131
132func TestSelfDelegatePanics(cur realm, t *testing.T) {
133	resetState()
134	alice := testutils.TestAddress("alice")
135
136	testing.SetRealm(testing.NewUserRealm(alice))
137	Init(cross(cur), "A,B")
138
139	rec := revive(func() { Delegate(cross(cur), alice.String()) })
140	if rec == nil {
141		t.Fatal("expected panic on self-delegation")
142	}
143}
144
145func TestDelegateLoopPanics(cur realm, t *testing.T) {
146	resetState()
147	alice := testutils.TestAddress("alice")
148	bob := testutils.TestAddress("bob")
149	carol := testutils.TestAddress("carol")
150
151	testing.SetRealm(testing.NewUserRealm(alice))
152	Init(cross(cur), "A,B")
153	GiveRightToVote(cross(cur), bob.String())
154	GiveRightToVote(cross(cur), carol.String())
155
156	// alice -> bob
157	testing.SetRealm(testing.NewUserRealm(alice))
158	Delegate(cross(cur), bob.String())
159
160	// carol -> alice would close the loop carol -> alice -> bob if bob then
161	// delegated back to carol; instead exercise the direct A -> B -> A cycle:
162	testing.SetRealm(testing.NewUserRealm(carol))
163	rec := revive(func() {
164		// bob has no delegate yet (he's an accumulation point), so seed a
165		// cycle by having carol delegate to bob, then bob delegate to carol.
166		Delegate(cross(cur), bob.String())
167	})
168	if rec != nil {
169		t.Fatal("carol -> bob should succeed (bob hasn't voted or delegated)")
170	}
171
172	testing.SetRealm(testing.NewUserRealm(bob))
173	rec = revive(func() { Delegate(cross(cur), carol.String()) })
174	if rec == nil {
175		t.Fatal("expected panic when closing a delegation loop (bob -> carol -> bob)")
176	}
177}
178
179func TestWinningProposalTieBreaksLowestIndex(cur realm, t *testing.T) {
180	resetState()
181	alice := testutils.TestAddress("alice")
182	bob := testutils.TestAddress("bob")
183
184	testing.SetRealm(testing.NewUserRealm(alice))
185	Init(cross(cur), "A,B,C")
186	GiveRightToVote(cross(cur), bob.String())
187	Vote(cross(cur), 1)
188
189	testing.SetRealm(testing.NewUserRealm(bob))
190	Vote(cross(cur), 0)
191
192	if WinningProposal() != 0 {
193		t.Fatalf("WinningProposal() = %d, want 0 (tie broken toward lowest index)", WinningProposal())
194	}
195}
196
197// Non-crossing: Render is a plain read.
198func TestRenderBeforeInit(t *testing.T) {
199	resetState()
200	if out := Render(""); out == "" {
201		t.Fatal(`Render("") should not be empty before Init`)
202	}
203}
204
205func TestRenderAfterInit(cur realm, t *testing.T) {
206	resetState()
207	alice := testutils.TestAddress("alice")
208
209	testing.SetRealm(testing.NewUserRealm(alice))
210	Init(cross(cur), "A,B")
211	Vote(cross(cur), 0)
212
213	out := Render("")
214	if !strings.Contains(out, "A") || !strings.Contains(out, alice.String()) {
215		t.Fatal("Render should include the proposal names and the chairperson address")
216	}
217}