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}