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.

machine_test.gno

6.88 Kb · 236 lines
  1package bf
  2
  3import (
  4	"testing"
  5
  6	"gno.land/p/nym-moultest123/x/vm/vmkit/v0"
  7	"gno.land/p/nt/uassert/v0"
  8)
  9
 10// oneShot runs src to completion and returns what it wrote.
 11func oneShot(t *testing.T, src string) string {
 12	t.Helper()
 13	h := vmkit.NewTestHost()
 14	status, err := Run(src, h)
 15	uassert.NoError(t, err)
 16	uassert.Equal(t, "halted", status.String())
 17	return h.OutString()
 18}
 19
 20// TestSlicedRunEqualsOneShot is the property that makes a guest program a
 21// contract rather than a function call: chopping the execution into fuel
 22// slices, snapshotting between each, must produce exactly the single run.
 23func TestSlicedRunEqualsOneShot(t *testing.T) {
 24	for _, slice := range []int64{1, 2, 7, 64} {
 25		prog, err := CompileDefault(hello)
 26		uassert.NoError(t, err)
 27
 28		h := vmkit.NewTestHost()
 29		snap := []byte(nil)
 30		status := vmkit.Running
 31		slices := 0
 32		for status == vmkit.Running && slices < 10000 {
 33			m := NewMachine(prog) // a realm holds bytes, not a machine
 34			if snap != nil {
 35				uassert.NoError(t, m.Restore(snap))
 36			}
 37			_, status = m.Step(h, slice)
 38			snap = m.Snapshot()
 39			slices++
 40		}
 41		uassert.Equal(t, "halted", status.String())
 42		uassert.Equal(t, "Hello World", h.OutString())
 43	}
 44}
 45
 46func TestSlicedRunAgreesAcrossTheCorpus(t *testing.T) {
 47	for _, tc := range corpus {
 48		prog, err := CompileDefault(tc.src)
 49		uassert.NoError(t, err)
 50		if prog == nil {
 51			continue
 52		}
 53		h := vmkit.NewTestHost()
 54		snap := []byte(nil)
 55		status := vmkit.Running
 56		n := 0
 57		for status == vmkit.Running && n < 20000 {
 58			m := NewMachine(prog)
 59			if snap != nil {
 60				uassert.NoError(t, m.Restore(snap))
 61			}
 62			_, status = m.Step(h, 3)
 63			snap = m.Snapshot()
 64			n++
 65		}
 66		if h.OutString() != tc.want {
 67			t.Errorf("%s sliced: got %q, want %q", tc.name, h.OutString(), tc.want)
 68		}
 69	}
 70}
 71
 72func TestFuelStopsTheMachine(t *testing.T) {
 73	prog, err := CompileDefault(hello)
 74	uassert.NoError(t, err)
 75	m := NewMachine(prog)
 76	h := vmkit.NewTestHost()
 77
 78	used, status := m.Step(h, 5)
 79	uassert.Equal(t, int64(5), used)
 80	uassert.Equal(t, "running", status.String())
 81	uassert.Equal(t, "", h.OutString()) // not far enough to print anything
 82}
 83
 84// TestScanIsChargedPerCell pins the thing that would otherwise be a way to
 85// buy unbounded work for one unit: a scan walks one cell per unit of fuel,
 86// and stopping mid-scan resumes the same walk.
 87func TestScanIsChargedPerCell(t *testing.T) {
 88	// 20 non-zero cells, then scan left back to the zero at cell 0.
 89	src := ">+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+>+[<]"
 90	prog, err := CompileDefault(src)
 91	uassert.NoError(t, err)
 92
 93	m := NewMachine(prog)
 94	h := vmkit.NewTestHost()
 95	used, status := m.Step(h, 45)
 96	uassert.Equal(t, int64(45), used)
 97	uassert.Equal(t, "running", status.String())
 98	uassert.True(t, m.Pointer() > 0) // stopped part way down the scan
 99
100	// Resuming finishes the same walk and lands on the zero cell.
101	_, status = m.Step(h, vmkit.Unmetered)
102	uassert.Equal(t, "halted", status.String())
103	uassert.Equal(t, 0, m.Pointer())
104}
105
106func TestSnapshotRoundTrip(t *testing.T) {
107	prog, err := CompileDefault(hello)
108	uassert.NoError(t, err)
109
110	a := NewMachine(prog)
111	h := vmkit.NewTestHost()
112	a.Step(h, 40)
113
114	b := NewMachine(prog)
115	uassert.NoError(t, b.Restore(a.Snapshot()))
116	uassert.Equal(t, a.PC(), b.PC())
117	uassert.Equal(t, a.Pointer(), b.Pointer())
118	uassert.Equal(t, a.Touched(), b.Touched())
119	for i := 0; i < a.Touched(); i++ {
120		uassert.Equal(t, uint64(a.Cell(i)), uint64(b.Cell(i)))
121	}
122	uassert.Equal(t, string(a.Snapshot()), string(b.Snapshot()))
123}
124
125// TestSnapshotIsTrimmed is the continuation kill criterion in test form: if a
126// pause carried the whole 30,000 byte tape, resuming would cost more than
127// re-running and continuations would be theater.
128func TestSnapshotIsTrimmed(t *testing.T) {
129	m, err := Load(hello)
130	uassert.NoError(t, err)
131	h := vmkit.NewTestHost()
132	m.Step(h, vmkit.Unmetered)
133
134	uassert.Equal(t, 5, m.Touched()) // hello world uses five cells
135	snap := m.Snapshot()
136	// 4 magic + 1 version + 3x8 zig-zag ints + 1 status + 4 empty trap
137	// + 4 length + 5 tape. The README quotes this number.
138	uassert.Equal(t, 43, len(snap))
139	uassert.True(t, len(snap) < TapeSize/100)
140}
141
142func TestRestoreRejectsJunk(t *testing.T) {
143	prog, err := CompileDefault(hello)
144	uassert.NoError(t, err)
145	good := NewMachine(prog)
146	good.Step(vmkit.NewTestHost(), 20)
147	snap := good.Snapshot()
148
149	cases := []struct {
150		name string
151		b    []byte
152	}{
153		{"empty", []byte{}},
154		{"truncated", snap[:len(snap)-3]},
155		{"bad magic", append([]byte{0, 0, 0, 0}, snap[4:]...)},
156	}
157	for _, tc := range cases {
158		m := NewMachine(prog)
159		if err := m.Restore(tc.b); err == nil {
160			t.Errorf("Restore(%s) should have failed", tc.name)
161		}
162	}
163
164	// A snapshot with a wrong version byte is refused rather than decoded
165	// into a plausible-looking wrong state.
166	bad := make([]byte, len(snap))
167	copy(bad, snap)
168	bad[4] = 99
169	m := NewMachine(prog)
170	uassert.ErrorIs(t, m.Restore(bad), vmkit.ErrBadSnapshot)
171}
172
173func TestRestoreRejectsAnOutOfRangePC(t *testing.T) {
174	small, err := CompileDefault("+")
175	uassert.NoError(t, err)
176	big, err := CompileDefault(hello)
177	uassert.NoError(t, err)
178
179	m := NewMachine(big)
180	m.Step(vmkit.NewTestHost(), 30)
181	snap := m.Snapshot()
182
183	// The same snapshot against a two-op program points past the end.
184	other := NewMachine(small)
185	uassert.ErrorIs(t, other.Restore(snap), vmkit.ErrBadSnapshot)
186}
187
188func TestHaltedMachineStaysHalted(t *testing.T) {
189	m, err := Load("+++.")
190	uassert.NoError(t, err)
191	h := vmkit.NewTestHost()
192	m.Step(h, vmkit.Unmetered)
193	uassert.Equal(t, "halted", m.Status().String())
194
195	used, status := m.Step(h, vmkit.Unmetered)
196	uassert.Equal(t, int64(0), used)
197	uassert.Equal(t, "halted", status.String())
198	uassert.Equal(t, "\x03", h.OutString()) // still only one write
199}
200
201func TestLoadRejectsBadSource(t *testing.T) {
202	_, err := Load("[[")
203	uassert.Error(t, err)
204	status, err := Run("]", vmkit.NewTestHost())
205	uassert.Error(t, err)
206	uassert.Equal(t, "trapped", status.String())
207}
208
209// TestInstanceIntegration wires the machine through vmkit's realm-facing
210// plumbing, which is the shape the demo realm uses.
211func TestInstanceIntegration(t *testing.T) {
212	prog, err := CompileDefault(hello)
213	uassert.NoError(t, err)
214
215	inst := vmkit.NewInstance("001", address("g1x"), VMName, []byte(hello), vmkit.Unmetered)
216	h := vmkit.NewTestHost()
217	for i := 0; i < 200 && inst.Status == vmkit.Running; i++ {
218		uassert.NoError(t, inst.Run(NewMachine(prog), h, 8))
219	}
220	uassert.Equal(t, "halted", inst.Status.String())
221	uassert.Equal(t, "Hello World", h.OutString())
222	uassert.True(t, inst.Slices > 1)
223	uassert.True(t, inst.FuelUsed > 0)
224}
225
226func TestInstanceHonoursItsBudget(t *testing.T) {
227	prog, err := CompileDefault(hello)
228	uassert.NoError(t, err)
229
230	inst := vmkit.NewInstance("002", address("g1x"), VMName, []byte(hello), 10)
231	h := vmkit.NewTestHost()
232	uassert.NoError(t, inst.Run(NewMachine(prog), h, vmkit.Unmetered))
233	uassert.Equal(t, "out of fuel", inst.Status.String())
234	uassert.Equal(t, int64(10), inst.FuelUsed)
235	uassert.ErrorIs(t, inst.Run(NewMachine(prog), h, vmkit.Unmetered), vmkit.ErrBudgetExhausted)
236}