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

bfdemo_test.gno

4.88 Kb · 161 lines
  1package bfdemo
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"gno.land/p/moul/x/vm/vmkit/v0"
  8	"gno.land/p/nt/avl/v0"
  9	"gno.land/p/nt/seqid/v0"
 10	"gno.land/p/nt/testutils/v0"
 11	"gno.land/p/nt/uassert/v0"
 12)
 13
 14const hello = "++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------."
 15
 16// resetState puts the realm globals back where init() left them. Realm state
 17// persists for the whole test binary, so every test that asserts on ids or on
 18// the rendered instance list has to start from here.
 19func resetState() {
 20	store = vmkit.NewStore()
 21	inputs = avl.NewTree()
 22	idgen = seqid.ID(0)
 23	count = 0
 24}
 25
 26func TestUploadAndRunToCompletion(cur realm, t *testing.T) {
 27	resetState()
 28	alice := testutils.TestAddress("alice")
 29	testing.SetRealm(testing.NewUserRealm(alice))
 30
 31	id := Upload(cross(cur), hello, "", 0)
 32	uassert.Equal(t, "halted", Step(cross(cur), id, 0))
 33
 34	inst := store.Get(id)
 35	uassert.Equal(t, "Hello World", string(inst.Output))
 36	uassert.Equal(t, int64(1), inst.Slices)
 37	uassert.Equal(t, alice, inst.Owner)
 38}
 39
 40// TestOneProgramAcrossManyTransactions is what the realm exists to
 41// demonstrate: the same computation, finished over several calls, each paying
 42// for its own slice.
 43func TestOneProgramAcrossManyTransactions(cur realm, t *testing.T) {
 44	resetState()
 45	testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("alice")))
 46
 47	id := Upload(cross(cur), hello, "", 0)
 48	status := ""
 49	slices := 0
 50	for slices < 500 {
 51		status = Step(cross(cur), id, 7)
 52		slices++
 53		if status != "running" {
 54			break
 55		}
 56	}
 57
 58	inst := store.Get(id)
 59	uassert.Equal(t, "halted", status)
 60	uassert.Equal(t, "Hello World", string(inst.Output))
 61	uassert.True(t, inst.Slices > 1, "took more than one slice")
 62}
 63
 64func TestInputIsReadThroughTheHost(cur realm, t *testing.T) {
 65	resetState()
 66	testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("alice")))
 67
 68	// ",[.,]" echoes its input back.
 69	id := Upload(cross(cur), ",[.,]", "gno.land", 0)
 70	uassert.Equal(t, "halted", Step(cross(cur), id, 0))
 71	uassert.Equal(t, "gno.land", string(store.Get(id).Output))
 72}
 73
 74func TestBudgetStopsAProgram(cur realm, t *testing.T) {
 75	resetState()
 76	testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("alice")))
 77
 78	id := Upload(cross(cur), hello, "", 12)
 79	uassert.Equal(t, "out of fuel", Step(cross(cur), id, 0))
 80	uassert.Equal(t, int64(12), store.Get(id).FuelUsed)
 81
 82	// And it stays stopped rather than quietly continuing.
 83	uassert.AbortsContains(t, cur, "instance is out of fuel", func() {
 84		Step(cross(cur), id, 0)
 85	})
 86}
 87
 88func TestUploadRejectsAMalformedProgram(cur realm, t *testing.T) {
 89	resetState()
 90	testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("alice")))
 91
 92	// Rejected by the transaction that submitted it, not by whoever pays
 93	// for the first slice later.
 94	uassert.AbortsContains(t, cur, "unmatched", func() {
 95		Upload(cross(cur), "+++[", "", 0)
 96	})
 97	uassert.Equal(t, 0, count)
 98}
 99
100func TestRemoveIsOwnerOnly(cur realm, t *testing.T) {
101	resetState()
102	alice := testutils.TestAddress("alice")
103	bob := testutils.TestAddress("bob")
104
105	testing.SetRealm(testing.NewUserRealm(alice))
106	id := Upload(cross(cur), hello, "", 0)
107
108	testing.SetRealm(testing.NewUserRealm(bob))
109	uassert.AbortsContains(t, cur, "not your instance", func() {
110		Remove(cross(cur), id)
111	})
112
113	// Anyone may pay for a slice, though: the program and its budget were
114	// both fixed at upload.
115	uassert.Equal(t, "halted", Step(cross(cur), id, 0))
116
117	testing.SetRealm(testing.NewUserRealm(alice))
118	Remove(cross(cur), id)
119	uassert.Equal(t, 0, count)
120	uassert.True(t, store.Get(id) == nil)
121}
122
123func TestOutputIsCappedAndTrapped(cur realm, t *testing.T) {
124	resetState()
125	testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("alice")))
126
127	// A cell set to 1 and printed forever: an infinite writer.
128	id := Upload(cross(cur), "+[.]", "", 0)
129	status := Step(cross(cur), id, 0)
130
131	inst := store.Get(id)
132	uassert.Equal(t, "trapped", status)
133	uassert.Equal(t, "output limit reached", inst.Trap)
134	uassert.Equal(t, MaxOutput, len(inst.Output))
135}
136
137func TestRenderUnknownInstance(t *testing.T) {
138	resetState()
139	uassert.True(t, strings.Contains(Render("/nope"), "No such instance"))
140}
141
142// TestRenderEscapesGuestOutput pins the rule that a realm cannot be fixed in
143// place once it is live: everything a caller controls is escaped before it
144// reaches the page.
145func TestRenderEscapesGuestOutput(cur realm, t *testing.T) {
146	resetState()
147	testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("alice")))
148
149	// Print a backtick (96), which would otherwise close the code fence.
150	src := strings.Repeat("+", 96) + "."
151	id := Upload(cross(cur), src, "", 0)
152	Step(cross(cur), id, 0)
153
154	page := Render("/" + id)
155	uassert.Equal(t, "`", string(store.Get(id).Output))
156	uassert.True(t, strings.Contains(page, "\\x60"), "the backtick is escaped")
157
158	// The comment text in a program never reaches the page at all: only
159	// the eight operators do.
160	uassert.Equal(t, "+++[-]<>", operatorsOnly("+++ this is a comment [-] </script>"))
161}