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

bfdemo_test.gno

7.21 Kb · 232 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}
162
163// Output sits in a code span, where markdown escapes show verbatim: only the
164// pipe is escaped there, and nothing else.
165func TestCodeCell(t *testing.T) {
166	uassert.Equal(t, "`Hello, gno.land\\n`", codeCell("Hello, gno.land\\n"))
167	uassert.Equal(t, "`a\\|b`", codeCell("a|b"))
168}
169
170// emit returns a program that prints s, one byte at a time, so a test can put
171// any text in the output column without hand-writing Brainfuck.
172func emit(s string) string {
173	var sb strings.Builder
174	for i := 0; i < len(s); i++ {
175		sb.WriteString("[-]" + strings.Repeat("+", int(s[i])) + ".")
176	}
177	return sb.String()
178}
179
180// instanceRow returns the first instance row of the home page.
181func instanceRow(t *testing.T) string {
182	for _, line := range strings.Split(Render(""), "\n") {
183		if strings.HasPrefix(line, "| [") {
184			return line
185		}
186	}
187	t.Fatalf("no instance row on the home page")
188	return ""
189}
190
191// The output column is pinned on the rendered page, not only through the
192// helper, so the call site cannot drift back to ui.Cell inside backticks
193// (which would show `a\.b`) with the suite green.
194func TestRenderOutputCell(cur realm, t *testing.T) {
195	cases := []struct {
196		name, out, cell string
197	}{
198		{"an escapable character shows as written", "a.b", "| `a.b` |"},
199		{"no output is an empty cell, not two backticks", "", "| 0 |  |"},
200		{"a space at each end is padded in the markdown, past CommonMark's strip", " a ", "| `  a  ` |"},
201		{"a guest pipe stays inside its cell", "a|b", "| `a\\|b` |"},
202	}
203	for _, c := range cases {
204		resetState()
205		testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("alice")))
206		id := Upload(cross(cur), emit(c.out), "", 0)
207		if c.out != "" {
208			Step(cross(cur), id, 0)
209			uassert.Equal(t, c.out, string(store.Get(id).Output))
210		}
211		row := instanceRow(t)
212		uassert.True(t, strings.HasSuffix(row, c.cell), c.name+": "+row)
213	}
214}
215
216// codeCell follows the three rules of a code span, not only the pipe: empty
217// content is not a span, a space at either end needs a pad, and the fence
218// outscans any backtick run in the content.
219func TestCodeCellRules(t *testing.T) {
220	cases := []struct{ in, want string }{
221		{"", ""},
222		{"Hello World", "`Hello World`"},
223		{"a|b", "`a\\|b`"},
224		{" hi ", "`  hi  `"},
225		{" hi", "`  hi `"},
226		{"hi ", "` hi  `"},
227		{"a`b", "``a`b``"},
228	}
229	for _, c := range cases {
230		uassert.Equal(t, c.want, codeCell(c.in), "codeCell("+c.in+")")
231	}
232}