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

riscvdemo_test.gno

5.91 Kb · 193 lines
  1package riscvdemo
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"gno.land/p/moul/x/vm/riscv/v0"
  8	"gno.land/p/moul/x/vm/vmkit/v0"
  9	"gno.land/p/nt/avl/v0"
 10	"gno.land/p/nt/seqid/v0"
 11	"gno.land/p/nt/testutils/v0"
 12	"gno.land/p/nt/uassert/v0"
 13)
 14
 15// resetState puts the realm globals back where init() left them. Realm state
 16// persists for the whole test binary, so every test that asserts on ids or on
 17// the rendered instance list has to start from here.
 18func resetState() {
 19	store = vmkit.NewStore()
 20	inputs = avl.NewTree()
 21	idgen = seqid.ID(0)
 22	count = 0
 23}
 24
 25func alice() address { return testutils.TestAddress("alice") }
 26
 27func TestUploadAndRunToCompletion(cur realm, t *testing.T) {
 28	resetState()
 29	testing.SetRealm(testing.NewUserRealm(alice()))
 30
 31	id := UploadSample(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, gno.land\n", string(inst.Output))
 36	uassert.Equal(t, int64(1), inst.Slices)
 37	uassert.Equal(t, alice(), inst.Owner)
 38	uassert.Equal(t, "riscv32im", inst.VM)
 39}
 40
 41// Every sample on the front page has to reach its documented answer, or the
 42// page offers a button that traps when pressed. The words are assembled
 43// outside this repo, so this is the test that catches a mistyped one.
 44func TestEverySampleReachesItsAnswer(cur realm, t *testing.T) {
 45	resetState()
 46	testing.SetRealm(testing.NewUserRealm(alice()))
 47
 48	cases := []struct {
 49		slug   string
 50		reg    int
 51		want   uint32
 52		output string
 53	}{
 54		{"hello", 0, 0, "Hello, gno.land\n"},
 55		{"sum100", 5, 5050, ""},
 56		{"heavy", 5, 1250025000, ""},
 57		{"mext", 5, 3000000021, ""},
 58		{"clang", 0, 0, "fb7ffba0"},
 59		{"ledger", 0, 0, "alice 70\nbob 80\n"},
 60	}
 61	for _, c := range cases {
 62		id := UploadSample(cross(cur), c.slug, sampleByName(c.slug).input, 0)
 63		status := ""
 64		for i := 0; i < 20; i++ {
 65			status = Step(cross(cur), id, 0)
 66			if status != "running" {
 67				break
 68			}
 69		}
 70		uassert.Equal(t, "halted", status, c.slug+" must halt")
 71		inst := store.Get(id)
 72		uassert.Equal(t, c.output, string(inst.Output), c.slug+" output")
 73		if c.reg != 0 {
 74			var m riscv.Machine
 75			uassert.NoError(t, m.Restore(inst.Snapshot))
 76			uassert.Equal(t, uint64(c.want), uint64(m.Registers()[c.reg]), c.slug+" result")
 77		}
 78	}
 79}
 80
 81// TestOneProgramAcrossManyTransactions is what the realm exists to
 82// demonstrate: the same computation, finished over several calls, each paying
 83// for its own slice.
 84func TestOneProgramAcrossManyTransactions(cur realm, t *testing.T) {
 85	resetState()
 86	testing.SetRealm(testing.NewUserRealm(alice()))
 87
 88	id := UploadSample(cross(cur), "heavy", "", 0)
 89	status := ""
 90	for i := 0; i < 50; i++ {
 91		status = Step(cross(cur), id, 80000)
 92		if status != "running" {
 93			break
 94		}
 95	}
 96	inst := store.Get(id)
 97	uassert.Equal(t, "halted", status)
 98	uassert.True(t, inst.Slices > 1, "a 200k instruction program must not finish in one slice")
 99	uassert.Equal(t, int64(200006), inst.FuelUsed)
100}
101
102func TestUploadTakesHex(cur realm, t *testing.T) {
103	resetState()
104	testing.SetRealm(testing.NewUserRealm(alice()))
105
106	// addi a7, zero, 93 ; ecall
107	id := Upload(cross(cur), "0x9308d005 73000000", "", 0)
108	_ = id
109}
110
111func TestUploadRejectsBadImages(cur realm, t *testing.T) {
112	resetState()
113	testing.SetRealm(testing.NewUserRealm(alice()))
114
115	// Called without cross(): a panic raised through a crossing call cannot be
116	// recovered, so uassert would never see it.
117	uassert.PanicsWithMessage(t, cur, "riscvdemo: image is not valid hex", func() {
118		Upload(cur, "zzzz", "", 0)
119	})
120	uassert.PanicsWithMessage(t, cur, "riscvdemo: image is not valid hex", func() {
121		Upload(cur, "abc", "", 0)
122	})
123	uassert.PanicsWithMessage(t, cur, "riscvdemo: no such sample", func() {
124		UploadSample(cur, "nope", "", 0)
125	})
126	uassert.PanicsWithMessage(t, cur,
127		"riscvdemo: riscv: image length is not a whole number of instructions",
128		func() { Upload(cur, "0011", "", 0) })
129}
130
131func TestDecodeHex(t *testing.T) {
132	b, ok := decodeHex("0xDEADbeef")
133	uassert.True(t, ok)
134	uassert.Equal(t, 4, len(b))
135	uassert.Equal(t, uint64(0xDE), uint64(b[0]))
136	uassert.Equal(t, uint64(0xEF), uint64(b[3]))
137
138	_, ok = decodeHex("")
139	uassert.False(t, ok)
140	_, ok = decodeHex("0x")
141	uassert.False(t, ok)
142	// Whitespace is skipped, because a pasted hexdump has it.
143	b, ok = decodeHex("00 11\n22\t33")
144	uassert.True(t, ok)
145	uassert.Equal(t, 4, len(b))
146	// An odd number of digits is still a refusal, whitespace or not.
147	_, ok = decodeHex("00 1")
148	uassert.False(t, ok)
149}
150
151func TestRenderHomeOffersEverySample(cur realm, t *testing.T) {
152	resetState()
153	out := Render("")
154	uassert.True(t, strings.Contains(out, "RV32IM, on chain"), "the title")
155	for _, s := range samples {
156		uassert.True(t, strings.Contains(out, s.name), "home must offer "+s.slug)
157	}
158	uassert.True(t, strings.Contains(out, "No instances yet"), "the empty state")
159}
160
161// The register file is the interesting half of this VM: a program that writes
162// nothing still computed something, and the snapshot is the only place it is
163// legible between transactions.
164func TestRenderInstanceShowsTheRegisterFile(cur realm, t *testing.T) {
165	resetState()
166	testing.SetRealm(testing.NewUserRealm(alice()))
167
168	id := UploadSample(cross(cur), "sum100", "", 0)
169	Step(cross(cur), id, 0)
170
171	out := Render("/" + id)
172	uassert.True(t, strings.Contains(out, "## Registers"), "the section")
173	uassert.True(t, strings.Contains(out, "5050"), "t0 must show the answer")
174	uassert.True(t, strings.Contains(out, "halted"), "the status")
175}
176
177func TestRenderUnknownInstance(t *testing.T) {
178	uassert.True(t, strings.Contains(Render("/nope"), "No such instance"))
179}
180
181// ExampleRender pins the realm's output for a path whose content cannot drift
182// with the instance list. The home page and the instance page both produce
183// consecutive blank lines, which an example block cannot represent, so they
184// are asserted in the tests above instead.
185func ExampleRender() {
186	print(Render("/missing"))
187	// Output:
188	// # Instance missing
189	//
190	// [← all instances](/r/moul/x/vm/riscvdemo/v0)
191	//
192	// **No such instance.** It was never uploaded, or its owner removed it.
193}