524 lines
12 KiB
Go
524 lines
12 KiB
Go
package vm
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"testing"
|
|
|
|
"git.defalsify.org/festive/cache"
|
|
"git.defalsify.org/festive/render"
|
|
"git.defalsify.org/festive/resource"
|
|
"git.defalsify.org/festive/state"
|
|
)
|
|
|
|
var dynVal = "three"
|
|
|
|
type TestResource struct {
|
|
resource.MenuResource
|
|
state *state.State
|
|
}
|
|
|
|
func getOne(sym string, input []byte, ctx context.Context) (resource.Result, error) {
|
|
return resource.Result{
|
|
Content: "one",
|
|
}, nil
|
|
}
|
|
|
|
func getTwo(sym string, input []byte, ctx context.Context) (resource.Result, error) {
|
|
return resource.Result{
|
|
Content: "two",
|
|
}, nil
|
|
}
|
|
|
|
func getDyn(sym string, input []byte, ctx context.Context) (resource.Result, error) {
|
|
return resource.Result{
|
|
Content: dynVal,
|
|
}, nil
|
|
}
|
|
|
|
func getEcho(sym string, input []byte, ctx context.Context) (resource.Result, error) {
|
|
r := fmt.Sprintf("echo: %s", input)
|
|
return resource.Result{
|
|
Content: r,
|
|
}, nil
|
|
}
|
|
|
|
func setFlag(sym string, input []byte, ctx context.Context) (resource.Result, error) {
|
|
s := fmt.Sprintf("ping")
|
|
r := resource.Result{
|
|
Content: s,
|
|
}
|
|
if len(input) > 0 {
|
|
r.FlagSet = append(r.FlagSet, uint32(input[0]))
|
|
}
|
|
if len(input) > 1 {
|
|
r.FlagReset = append(r.FlagReset, uint32(input[1]))
|
|
}
|
|
log.Printf("setflag %v", r)
|
|
return r, nil
|
|
|
|
}
|
|
|
|
type TestStatefulResolver struct {
|
|
state *state.State
|
|
}
|
|
|
|
func (r TestResource) GetTemplate(sym string) (string, error) {
|
|
switch sym {
|
|
case "foo":
|
|
return "inky pinky blinky clyde", nil
|
|
case "bar":
|
|
return "inky pinky {{.one}} blinky {{.two}} clyde", nil
|
|
case "baz":
|
|
return "inky pinky {{.baz}} blinky clyde", nil
|
|
case "three":
|
|
return "{{.one}} inky pinky {{.three}} blinky clyde {{.two}}", nil
|
|
case "root":
|
|
return "root", nil
|
|
case "_catch":
|
|
return "aiee", nil
|
|
case "flagCatch":
|
|
return "flagiee", nil
|
|
}
|
|
panic(fmt.Sprintf("unknown symbol %s", sym))
|
|
return "", fmt.Errorf("unknown symbol %s", sym)
|
|
}
|
|
|
|
func (r TestResource) FuncFor(sym string) (resource.EntryFunc, error) {
|
|
switch sym {
|
|
case "one":
|
|
return getOne, nil
|
|
case "two":
|
|
return getTwo, nil
|
|
case "dyn":
|
|
return getDyn, nil
|
|
case "arg":
|
|
return r.getInput, nil
|
|
case "echo":
|
|
return getEcho, nil
|
|
case "setFlagOne":
|
|
return setFlag, nil
|
|
}
|
|
return nil, fmt.Errorf("invalid function: '%s'", sym)
|
|
}
|
|
|
|
func(r TestResource) getInput(sym string, input []byte, ctx context.Context) (resource.Result, error) {
|
|
v, err := r.state.GetInput()
|
|
return resource.Result{
|
|
Content: string(v),
|
|
}, err
|
|
}
|
|
|
|
func(r TestResource) GetCode(sym string) ([]byte, error) {
|
|
var b []byte
|
|
switch sym {
|
|
case "_catch":
|
|
b = NewLine(b, MOUT, []string{"0", "repent"}, nil, nil)
|
|
b = NewLine(b, HALT, nil, nil, nil)
|
|
case "flagCatch":
|
|
b = NewLine(b, MOUT, []string{"0", "repent"}, nil, nil)
|
|
b = NewLine(b, HALT, nil, nil, nil)
|
|
}
|
|
|
|
return b, nil
|
|
}
|
|
|
|
func TestRun(t *testing.T) {
|
|
st := state.NewState(5)
|
|
rs := TestResource{}
|
|
ca := cache.NewCache()
|
|
vm := NewVm(&st, &rs, ca, nil)
|
|
|
|
b := NewLine(nil, MOVE, []string{"foo"}, nil, nil)
|
|
b = NewLine(b, HALT, nil, nil, nil)
|
|
_, err := vm.Run(b, context.TODO())
|
|
if err != nil {
|
|
t.Errorf("run error: %v", err)
|
|
}
|
|
|
|
b = []byte{0x01, 0x02}
|
|
_, err = vm.Run(b, context.TODO())
|
|
if err == nil {
|
|
t.Errorf("no error on invalid opcode")
|
|
}
|
|
}
|
|
|
|
func TestRunLoadRender(t *testing.T) {
|
|
st := state.NewState(5)
|
|
rs := TestResource{}
|
|
ca := cache.NewCache()
|
|
vm := NewVm(&st, &rs, ca, nil)
|
|
|
|
st.Down("bar")
|
|
|
|
var err error
|
|
ctx := context.TODO()
|
|
b := NewLine(nil, LOAD, []string{"one"}, []byte{0x0a}, nil)
|
|
b = NewLine(b, MAP, []string{"one"}, nil, nil)
|
|
b = NewLine(b, LOAD, []string{"two"}, []byte{0x0a}, nil)
|
|
b = NewLine(b, MAP, []string{"two"}, nil, nil)
|
|
b = NewLine(b, HALT, nil, nil, nil)
|
|
b, err = vm.Run(b, ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r, err := vm.Render(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
expect := "inky pinky one blinky two clyde"
|
|
if r != expect {
|
|
t.Fatalf("Expected\n\t%s\ngot\n\t%s\n", expect, r)
|
|
}
|
|
|
|
b = NewLine(nil, LOAD, []string{"two"}, []byte{0x0a}, nil)
|
|
b = NewLine(b, MAP, []string{"two"}, nil, nil)
|
|
b = NewLine(b, HALT, nil, nil, nil)
|
|
b, err = vm.Run(b, ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b = NewLine(nil, MAP, []string{"one"}, nil, nil)
|
|
b = NewLine(b, HALT, nil, nil, nil)
|
|
_, err = vm.Run(b, ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r, err = vm.Render(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
expect = "inky pinky one blinky two clyde"
|
|
if r != expect {
|
|
t.Fatalf("Expected %v, got %v", expect, r)
|
|
}
|
|
}
|
|
|
|
func TestRunMultiple(t *testing.T) {
|
|
st := state.NewState(5)
|
|
rs := TestResource{}
|
|
ca := cache.NewCache()
|
|
vm := NewVm(&st, &rs, ca, nil)
|
|
|
|
ctx := context.TODO()
|
|
b := NewLine(nil, MOVE, []string{"test"}, nil, nil)
|
|
b = NewLine(b, LOAD, []string{"one"}, []byte{0x00}, nil)
|
|
b = NewLine(b, LOAD, []string{"two"}, []byte{42}, nil)
|
|
b = NewLine(b, HALT, nil, nil, nil)
|
|
b, err := vm.Run(b, ctx)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if len(b) > 0 {
|
|
t.Errorf("expected empty code")
|
|
}
|
|
}
|
|
|
|
func TestRunReload(t *testing.T) {
|
|
st := state.NewState(5)
|
|
rs := TestResource{}
|
|
ca := cache.NewCache()
|
|
szr := render.NewSizer(128)
|
|
vm := NewVm(&st, &rs, ca, szr)
|
|
|
|
ctx := context.TODO()
|
|
b := NewLine(nil, MOVE, []string{"root"}, nil, nil)
|
|
b = NewLine(b, LOAD, []string{"dyn"}, nil, []uint8{0})
|
|
b = NewLine(b, MAP, []string{"dyn"}, nil, nil)
|
|
b = NewLine(b, HALT, nil, nil, nil)
|
|
_, err := vm.Run(b, ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r, err := vm.Render(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if r != "root" {
|
|
t.Fatalf("expected result 'root', got %v", r)
|
|
}
|
|
dynVal = "baz"
|
|
b = NewLine(nil, RELOAD, []string{"dyn"}, nil, nil)
|
|
b = NewLine(b, HALT, nil, nil, nil)
|
|
_, err = vm.Run(b, ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestHalt(t *testing.T) {
|
|
st := state.NewState(5)
|
|
rs := TestResource{}
|
|
ca := cache.NewCache()
|
|
vm := NewVm(&st, &rs, ca, nil)
|
|
|
|
b := NewLine(nil, MOVE, []string{"root"}, nil, nil)
|
|
b = NewLine(b, LOAD, []string{"one"}, nil, []uint8{0})
|
|
b = NewLine(b, HALT, nil, nil, nil)
|
|
b = NewLine(b, MOVE, []string{"foo"}, nil, nil)
|
|
var err error
|
|
b, err = vm.Run(b, context.TODO())
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
r, _ := st.Where()
|
|
if r == "foo" {
|
|
t.Fatalf("Expected where-symbol not to be 'foo'")
|
|
}
|
|
if !bytes.Equal(b[:2], []byte{0x00, MOVE}) {
|
|
t.Fatalf("Expected MOVE instruction, found '%v'", b)
|
|
}
|
|
}
|
|
|
|
func TestRunArg(t *testing.T) {
|
|
st := state.NewState(5)
|
|
rs := TestResource{}
|
|
ca := cache.NewCache()
|
|
vm := NewVm(&st, &rs, ca, nil)
|
|
|
|
input := []byte("bar")
|
|
_ = st.SetInput(input)
|
|
|
|
bi := NewLine(nil, INCMP, []string{"bar", "baz"}, nil, nil)
|
|
bi = NewLine(bi, HALT, nil, nil, nil)
|
|
b, err := vm.Run(bi, context.TODO())
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
l := len(b)
|
|
if l != 0 {
|
|
t.Errorf("expected empty remainder, got length %v: %v", l, b)
|
|
}
|
|
r, _ := st.Where()
|
|
if r != "baz" {
|
|
t.Errorf("expected where-state baz, got %v", r)
|
|
}
|
|
}
|
|
|
|
func TestRunInputHandler(t *testing.T) {
|
|
st := state.NewState(5)
|
|
rs := TestResource{}
|
|
ca := cache.NewCache()
|
|
vm := NewVm(&st, &rs, ca, nil)
|
|
|
|
_ = st.SetInput([]byte("baz"))
|
|
|
|
bi := NewLine([]byte{}, INCMP, []string{"bar", "aiee"}, nil, nil)
|
|
bi = NewLine(bi, INCMP, []string{"baz", "foo"}, nil, nil)
|
|
bi = NewLine(bi, LOAD, []string{"one"}, []byte{0x00}, nil)
|
|
bi = NewLine(bi, LOAD, []string{"two"}, []byte{0x03}, nil)
|
|
bi = NewLine(bi, MAP, []string{"one"}, nil, nil)
|
|
bi = NewLine(bi, MAP, []string{"two"}, nil, nil)
|
|
bi = NewLine(bi, HALT, nil, nil, nil)
|
|
|
|
var err error
|
|
_, err = vm.Run(bi, context.TODO())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r, _ := st.Where()
|
|
if r != "foo" {
|
|
t.Fatalf("expected where-sym 'foo', got '%v'", r)
|
|
}
|
|
}
|
|
|
|
func TestRunArgInvalid(t *testing.T) {
|
|
st := state.NewState(5)
|
|
rs := TestResource{}
|
|
ca := cache.NewCache()
|
|
vm := NewVm(&st, &rs, ca, nil)
|
|
|
|
_ = st.SetInput([]byte("foo"))
|
|
|
|
var err error
|
|
|
|
st.Down("root")
|
|
b := NewLine(nil, INCMP, []string{"bar", "baz"}, nil, nil)
|
|
|
|
b, err = vm.Run(b, context.TODO())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r, _ := st.Where()
|
|
if r != "_catch" {
|
|
t.Fatalf("expected where-state _catch, got %v", r)
|
|
}
|
|
}
|
|
|
|
func TestRunMenu(t *testing.T) {
|
|
st := state.NewState(5)
|
|
rs := TestResource{}
|
|
ca := cache.NewCache()
|
|
vm := NewVm(&st, &rs, ca, nil)
|
|
|
|
var err error
|
|
|
|
ctx := context.TODO()
|
|
|
|
b := NewLine(nil, MOVE, []string{"foo"}, nil, nil)
|
|
b = NewLine(b, MOUT, []string{"0", "one"}, nil, nil)
|
|
b = NewLine(b, MOUT, []string{"1", "two"}, nil, nil)
|
|
b = NewLine(b, HALT, nil, nil, nil)
|
|
|
|
b, err = vm.Run(b, ctx)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
l := len(b)
|
|
if l != 0 {
|
|
t.Errorf("expected empty remainder, got length %v: %v", l, b)
|
|
}
|
|
|
|
r, err := vm.Render(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
expect := "inky pinky blinky clyde\n0:one\n1:two"
|
|
if r != expect {
|
|
t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", expect, r)
|
|
}
|
|
}
|
|
|
|
func TestRunMenuBrowse(t *testing.T) {
|
|
log.Printf("This test is incomplete, it must check the output of a menu browser once one is implemented. For now it only checks whether it can execute the runner endpoints for the instrucitons.")
|
|
st := state.NewState(5)
|
|
rs := TestResource{}
|
|
ca := cache.NewCache()
|
|
vm := NewVm(&st, &rs, ca, nil)
|
|
|
|
var err error
|
|
|
|
ctx := context.TODO()
|
|
|
|
b := NewLine(nil, MOVE, []string{"foo"}, nil, nil)
|
|
b = NewLine(b, MOUT, []string{"0", "one"}, nil, nil)
|
|
b = NewLine(b, MOUT, []string{"1", "two"}, nil, nil)
|
|
b = NewLine(b, HALT, nil, nil, nil)
|
|
|
|
b, err = vm.Run(b, ctx)
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
l := len(b)
|
|
if l != 0 {
|
|
t.Errorf("expected empty remainder, got length %v: %v", l, b)
|
|
}
|
|
|
|
r, err := vm.Render(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
expect := "inky pinky blinky clyde\n0:one\n1:two"
|
|
if r != expect {
|
|
t.Fatalf("expected:\n\t%s\ngot:\n\t%s\n", expect, r)
|
|
}
|
|
}
|
|
|
|
func TestRunReturn(t *testing.T) {
|
|
st := state.NewState(5)
|
|
rs := TestResource{}
|
|
ca := cache.NewCache()
|
|
vm := NewVm(&st, &rs, ca, nil)
|
|
|
|
var err error
|
|
|
|
st.Down("root")
|
|
st.SetInput([]byte("0"))
|
|
b := NewLine(nil, INCMP, []string{"0", "bar"}, nil, nil)
|
|
b = NewLine(b, HALT, nil, nil, nil)
|
|
b = NewLine(b, INCMP, []string{"1", "_"}, nil, nil)
|
|
b = NewLine(b, HALT, nil, nil, nil)
|
|
|
|
ctx := context.TODO()
|
|
|
|
b, err = vm.Run(b, ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
location, _ := st.Where()
|
|
if location != "bar" {
|
|
t.Fatalf("expected location 'bar', got '%s'", location)
|
|
}
|
|
st.SetInput([]byte("1"))
|
|
b, err = vm.Run(b, ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
location, _ = st.Where()
|
|
if location != "root" {
|
|
t.Fatalf("expected location 'root', got '%s'", location)
|
|
}
|
|
}
|
|
|
|
|
|
func TestRunLoadInput(t *testing.T) {
|
|
st := state.NewState(5)
|
|
rs := TestResource{}
|
|
ca := cache.NewCache()
|
|
vm := NewVm(&st, &rs, ca, nil)
|
|
|
|
var err error
|
|
|
|
st.Down("root")
|
|
st.SetInput([]byte("foobar"))
|
|
|
|
b := NewLine(nil, LOAD, []string{"echo"}, []byte{0x00}, nil)
|
|
b = NewLine(b, HALT, nil, nil, nil)
|
|
|
|
ctx := context.TODO()
|
|
|
|
b, err = vm.Run(b, ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
r, err := ca.Get("echo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if r != "echo: foobar" {
|
|
t.Fatalf("expected 'echo: foobar', got %s", r)
|
|
}
|
|
}
|
|
|
|
func TestInputBranch(t *testing.T) {
|
|
st := state.NewState(5)
|
|
rs := TestResource{}
|
|
ca := cache.NewCache()
|
|
vm := NewVm(&st, &rs, ca, nil)
|
|
|
|
var err error
|
|
|
|
st.Down("root")
|
|
|
|
b := NewLine(nil, LOAD, []string{"setFlagOne"}, []byte{0x00}, nil)
|
|
b = NewLine(b, CATCH, []string{"flagCatch"}, []byte{8}, []uint8{0})
|
|
b = NewLine(b, RELOAD, []string{"setFlagOne"}, nil, nil)
|
|
b = NewLine(b, CATCH, []string{"flagCatch"}, []byte{8}, []uint8{0})
|
|
b = NewLine(b, CATCH, []string{"one"}, []byte{9}, []uint8{0})
|
|
|
|
ctx := context.TODO()
|
|
|
|
st.SetInput([]byte{0x08})
|
|
b, err = vm.Run(b, ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
location, _ := st.Where()
|
|
if location != "flagCatch" {
|
|
t.Fatalf("expected 'flagCatch', got %s", location)
|
|
}
|
|
|
|
st.SetInput([]byte{0x09, 0x08})
|
|
b, err = vm.Run(b, ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
location, _ = st.Where()
|
|
if location != "one" {
|
|
t.Fatalf("expected 'one', got %s", location)
|
|
}
|
|
}
|