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.

compact_test.gno

7.72 Kb · 235 lines
  1package compact
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"gno.land/p/nym-moultest123/kit/ui/v0"
  8	"gno.land/p/nym-moultest123/x/storagecost/v1"
  9	"gno.land/p/nt/testutils/v0"
 10	"gno.land/p/nt/uassert/v0"
 11)
 12
 13var (
 14	alice = testutils.TestAddress("alice")
 15	bob   = testutils.TestAddress("bob")
 16	// janitor never adds anything, it only compacts. The whole point of the
 17	// realm is that this is the address the chain pays.
 18	janitor = testutils.TestAddress("janitor")
 19)
 20
 21// drain empties the board and asserts it, so a test starts from a known
 22// state whatever ran before it. Indices are append-addressed for the life of
 23// the realm, so every test asserts on counts and never on absolute indices.
 24func drain(cur realm, t *testing.T) {
 25	t.Helper()
 26	for i := 0; i < entries.TotalSize(); i++ {
 27		if e, ok := entryAt(i); ok {
 28			testing.SetRealm(testing.NewUserRealm(e.Author))
 29			Drop(cross(cur), i)
 30		}
 31	}
 32	testing.SetRealm(testing.NewUserRealm(janitor))
 33	Compact(cross(cur))
 34	live, _ := Fragmentation()
 35	uassert.Equal(t, 0, live)
 36}
 37
 38func TestDropFreesNothingUntilCompact(cur realm, t *testing.T) {
 39	drain(cur, t)
 40	testing.SetRealm(testing.NewUserRealm(alice))
 41	for i := 0; i < 8; i++ {
 42		Add(cross(cur), "entry")
 43	}
 44	live, allocated := Fragmentation()
 45	uassert.Equal(t, 8, live)
 46	uassert.Equal(t, 0, Reclaimable())
 47
 48	// Dropping everything moves the live count but frees no nodes by itself.
 49	base := allocated - 8
 50	for i := base; i < allocated; i++ {
 51		Drop(cross(cur), i)
 52	}
 53	live, allocated2 := Fragmentation()
 54	uassert.Equal(t, 0, live)
 55	uassert.Equal(t, allocated, allocated2, "a soft delete must not change the index space")
 56	uassert.True(t, Reclaimable() > 0, "a fully dead tail should be reclaimable")
 57
 58	// And compaction is what actually frees them.
 59	testing.SetRealm(testing.NewUserRealm(janitor))
 60	freed := Compact(cross(cur))
 61	uassert.True(t, freed > 0)
 62	uassert.Equal(t, 0, Reclaimable())
 63	// Compacting twice is not an error, it just frees nothing, so two bots
 64	// racing for the same nodes both survive rather than one aborting.
 65	uassert.Equal(t, 0, Compact(cross(cur)))
 66}
 67
 68// TestScatteredHolesReclaimLessThanTheGap is the claim the Render makes, and
 69// the reason the realm publishes two numbers instead of one. No other test
 70// could catch it: they all drop contiguous runs.
 71func TestScatteredHolesReclaimLessThanTheGap(cur realm, t *testing.T) {
 72	drain(cur, t)
 73	testing.SetRealm(testing.NewUserRealm(alice))
 74	base := entries.TotalSize()
 75	for i := 0; i < 16; i++ {
 76		Add(cross(cur), "entry")
 77	}
 78	// Every other index, so no whole subtree dies.
 79	for i := base; i < base+16; i += 2 {
 80		Drop(cross(cur), i)
 81	}
 82	live, allocated := Fragmentation()
 83	gap := allocated - live
 84	uassert.True(t, gap >= 8, "eight holes were made")
 85	uassert.True(t, Reclaimable() < gap,
 86		"scattered holes must reclaim fewer nodes than the gap suggests")
 87
 88	drain(cur, t)
 89}
 90
 91func TestOnlyTheAuthorMayDrop(cur realm, t *testing.T) {
 92	drain(cur, t)
 93	testing.SetRealm(testing.NewUserRealm(alice))
 94	i := Add(cross(cur), "alice's entry")
 95
 96	testing.SetRealm(testing.NewUserRealm(bob))
 97	uassert.AbortsWithMessage(t, cur, "compact: only the author may drop an entry", func() {
 98		Drop(cross(cur), i)
 99	})
100	// But compaction stays permissionless, which is the asymmetry that makes
101	// the mechanism work: choosing what dies is owned, reclaiming is not.
102	testing.SetRealm(testing.NewUserRealm(janitor))
103	uassert.NotAborts(t, cur, func() { Compact(cross(cur)) })
104
105	drain(cur, t)
106}
107
108func TestAddRejectsBadInput(cur realm, t *testing.T) {
109	drain(cur, t)
110	testing.SetRealm(testing.NewUserRealm(alice))
111	uassert.AbortsWithMessage(t, cur, "compact: empty entry", func() {
112		Add(cross(cur), "")
113	})
114	uassert.AbortsWithMessage(t, cur, "compact: entry too long, 257 bytes against a 256 cap", func() {
115		Add(cross(cur), strings.Repeat("x", maxText+1))
116	})
117	uassert.AbortsContains(t, cur, "compact: no live entry at", func() {
118		Drop(cross(cur), 99999)
119	})
120}
121
122// TestQuoteCountsNodesRatherThanGuessingFromPayload is the design claim of the
123// realm. The quote must depend on the NODE COUNT and not on how fat the
124// entries were, which is exactly where a payload-derived bounty goes wrong.
125func TestQuoteCountsNodesRatherThanGuessingFromPayload(cur realm, t *testing.T) {
126	drain(cur, t)
127
128	// An empty board advertises nothing and must not claim a profit.
129	empty := Quote()
130	uassert.Equal(t, int64(0), empty.Bytes)
131	uassert.False(t, empty.Worth())
132
133	// Eight tiny entries, dropped and measured.
134	testing.SetRealm(testing.NewUserRealm(alice))
135	base := entries.TotalSize()
136	for i := 0; i < 8; i++ {
137		Add(cross(cur), "x")
138	}
139	for i := base; i < base+8; i++ {
140		Drop(cross(cur), i)
141	}
142	tiny := Quote()
143	tinyNodes := Reclaimable()
144	testing.SetRealm(testing.NewUserRealm(janitor))
145	Compact(cross(cur))
146
147	// Eight fat entries, same count, same treatment.
148	testing.SetRealm(testing.NewUserRealm(alice))
149	base = entries.TotalSize()
150	fat := strings.Repeat("y", maxText)
151	for i := 0; i < 8; i++ {
152		Add(cross(cur), fat)
153	}
154	for i := base; i < base+8; i++ {
155		Drop(cross(cur), i)
156	}
157	big := Quote()
158
159	bigNodes := Reclaimable()
160
161	// 256x the payload, and the quote is still purely a function of the node
162	// count. Not the same TOTAL: the two batches sit at different offsets in
163	// an append-addressed index space, so the tree shape differs and one
164	// reclaimed a node more than the other. That is the honest version of the
165	// claim, and asserting equal totals (which an earlier draft of this test
166	// did) only passed by luck of the offsets.
167	uassert.True(t, tinyNodes > 0 && bigNodes > 0)
168	uassert.Equal(t, storagecost.EstimateNodes(int64(tinyNodes)), tiny.Bytes)
169	uassert.Equal(t, storagecost.EstimateNodes(int64(bigNodes)), big.Bytes)
170
171	// The rate is what must not move: same bytes per reclaimed node whether
172	// the entries were 1 byte or 256. A payload-derived bounty would differ
173	// by 256x here, which is the failure this realm is built to avoid.
174	uassert.Equal(t, tiny.Bytes/int64(tinyNodes), big.Bytes/int64(bigNodes),
175		"bytes per reclaimed node must not depend on payload size")
176
177	drain(cur, t)
178}
179
180// TestRenderEscapesTheBoard: an entry is a caller's string landing in an
181// inline markdown slot, so it goes through ui.Excerpt or it is an injection.
182func TestRenderEscapesTheBoard(cur realm, t *testing.T) {
183	drain(cur, t)
184	testing.SetRealm(testing.NewUserRealm(alice))
185	Add(cross(cur), "[Claim 100 GNOT](https://evil.example)\n# Official")
186
187	out := Render("")
188	uassert.False(t, strings.Contains(out, "](https://evil.example)"), out)
189	uassert.False(t, strings.Contains(out, "\n# Official"), out)
190	uassert.True(t, strings.Contains(out, "Claim 100 GNOT"), out)
191	uassert.True(t, strings.Contains(out, ui.Addr(alice)), out)
192
193	drain(cur, t)
194}
195
196func TestRenderShowsBothIntegersAndTheAction(cur realm, t *testing.T) {
197	drain(cur, t)
198
199	out := Render("")
200	uassert.True(t, strings.Contains(out, "# Compact"), out)
201	uassert.True(t, strings.Contains(out, "Add the first entry"), out)
202	uassert.True(t, strings.Contains(out, "nothing to reclaim"), out)
203
204	testing.SetRealm(testing.NewUserRealm(alice))
205	base := entries.TotalSize()
206	for i := 0; i < 8; i++ {
207		Add(cross(cur), "entry")
208	}
209	for i := base; i < base+8; i++ {
210		Drop(cross(cur), i)
211	}
212	out = Render("")
213	uassert.True(t, strings.Contains(out, "reclaimable nodes"), out)
214	uassert.True(t, strings.Contains(out, "$help&func=Compact"), out)
215
216	drain(cur, t)
217}
218
219// TestRenderNeverEmitsTwoBlankLines is what lets ExampleRender stay
220// meaningful: gno collapses them, so output containing them can never be
221// pinned by an example.
222func TestRenderNeverEmitsTwoBlankLines(cur realm, t *testing.T) {
223	drain(cur, t)
224	testing.SetRealm(testing.NewUserRealm(alice))
225	i := Add(cross(cur), "one")
226	Add(cross(cur), "two")
227	Drop(cross(cur), i)
228
229	for _, path := range []string{"", "anything"} {
230		out := Render(path)
231		uassert.False(t, strings.Contains(out, "\n\n\n"), "blank-line run in Render("+path+")")
232	}
233
234	drain(cur, t)
235}