diff --git a/go/engine/engine.go b/go/engine/engine.go index 2f18b9b..9309ca8 100644 --- a/go/engine/engine.go +++ b/go/engine/engine.go @@ -2,6 +2,7 @@ package engine import ( "context" + "fmt" "io" "log" @@ -16,11 +17,11 @@ import ( //} type Engine struct { - st state.State + st *state.State rs resource.Resource } -func NewEngine(st state.State, rs resource.Resource) Engine { +func NewEngine(st *state.State, rs resource.Resource) Engine { engine := Engine{st, rs} return engine } @@ -28,7 +29,33 @@ func NewEngine(st state.State, rs resource.Resource) Engine { func(en *Engine) Init(ctx context.Context) error { b := vm.NewLine([]byte{}, vm.MOVE, []string{"root"}, nil, nil) var err error - en.st, _, err = vm.Run(b, en.st, en.rs, ctx) + _, err = vm.Run(b, en.st, en.rs, ctx) + if err != nil { + return err + } + location := en.st.Where() + code, err := en.rs.GetCode(location) + if err != nil { + return err + } + return en.st.AppendCode(code) +} + +func (en *Engine) Exec(input []byte, ctx context.Context) error { + l := uint8(len(input)) + if l > 255 { + return fmt.Errorf("input too long (%v)", l) + } + input = append([]byte{l}, input...) + code, err := en.st.GetCode() + if err != nil { + return err + } + if len(code) == 0 { + return fmt.Errorf("no code to execute") + } + code, err = vm.Apply(input, code, en.st, en.rs, ctx) + en.st.SetCode(code) return err } diff --git a/go/engine/engine_test.go b/go/engine/engine_test.go index 6da7e0a..8da3f78 100644 --- a/go/engine/engine_test.go +++ b/go/engine/engine_test.go @@ -3,6 +3,8 @@ package engine import ( "bytes" "context" + "fmt" + "io/ioutil" "log" "path" "text/template" @@ -16,10 +18,10 @@ import ( type FsWrapper struct { *resource.FsResource - st state.State + st *state.State } -func NewFsWrapper(path string, st state.State, ctx context.Context) FsWrapper { +func NewFsWrapper(path string, st *state.State, ctx context.Context) FsWrapper { rs := resource.NewFsResource(path, ctx) return FsWrapper { &rs, @@ -42,12 +44,27 @@ func (r FsWrapper) RenderTemplate(sym string, values map[string]string) (string, if err != nil { return "", err } - log.Printf("template is %v render is %v", v, b) return b.String(), err } +func(fs FsWrapper) one(ctx context.Context) (string, error) { + return "one", nil +} + func(fs FsWrapper) FuncFor(sym string) (resource.EntryFunc, error) { - return nil, nil + switch sym { + case "one": + return fs.one, nil + } + return nil, fmt.Errorf("function for %v not found", sym) +} + +func(fs FsWrapper) GetCode(sym string) ([]byte, error) { + sym += ".bin" + fp := path.Join(fs.Path, sym) + r, err := ioutil.ReadFile(fp) + log.Printf("getcode for %v %v", fp, r) + return r, err } func TestEngineInit(t *testing.T) { @@ -62,8 +79,8 @@ func TestEngineInit(t *testing.T) { // } dir := path.Join(testdataloader.GetBasePath(), "testdata") ctx := context.TODO() - rs := NewFsWrapper(dir, st, ctx) - en := NewEngine(st, rs) + rs := NewFsWrapper(dir, &st, ctx) + en := NewEngine(&st, &rs) err := en.Init(ctx) if err != nil { t.Fatal(err) @@ -78,4 +95,13 @@ func TestEngineInit(t *testing.T) { if !bytes.Equal(b, []byte("hello world")) { t.Fatalf("expected result 'hello world', got %v", b) } + input := []byte("foo") + err = en.Exec(input, ctx) + if err != nil { + t.Fatal(err) + } + r := st.Where() + if r != "bar" { + t.Fatalf("expected where-string 'bar', got %v", r) + } } diff --git a/go/resource/fs.go b/go/resource/fs.go index e31a853..1c71a8f 100644 --- a/go/resource/fs.go +++ b/go/resource/fs.go @@ -9,19 +9,19 @@ import ( ) type FsResource struct { - path string + Path string ctx context.Context } func NewFsResource(path string, ctx context.Context) (FsResource) { return FsResource{ - path: path, + Path: path, ctx: ctx, } } func(fs FsResource) GetTemplate(sym string) (string, error) { - fp := path.Join(fs.path, sym) + fp := path.Join(fs.Path, sym) r, err := ioutil.ReadFile(fp) s := string(r) return strings.TrimSpace(s), err diff --git a/go/state/state.go b/go/state/state.go index dfe535c..b4c3d97 100644 --- a/go/state/state.go +++ b/go/state/state.go @@ -26,7 +26,7 @@ type State struct { CacheUseSize uint32 // Currently used bytes by all values in cache Cache []map[string]string // All loaded cache items CacheMap map[string]string // Mapped - Code []byte // Pending bytecode to execute + code []byte // Pending bytecode to execute execPath []string // Command symbols stack arg *string // Optional argument. Nil if not set. sizes map[string]uint16 // Size limits for all loaded symbols. @@ -366,7 +366,7 @@ func(st *State) Check(key string) bool { return st.frameOf(key) == -1 } -// Returns size used by values, and remaining size available +// Size returns size used by values, and remaining size available func(st *State) Size() (uint32, uint32) { var l int var c uint16 @@ -378,6 +378,25 @@ func(st *State) Size() (uint32, uint32) { return r, uint32(c)-r } +// Appendcode adds the given bytecode to the end of the existing code. +func(st *State) AppendCode(b []byte) error { + st.code = append(st.code, b...) + log.Printf("code changed to %v", b) + return nil +} + +// SetCode replaces the current bytecode with the given bytecode. +func(st *State) SetCode(b []byte) { + log.Printf("code set to %v", b) + st.code = b +} + +func(st *State) GetCode() ([]byte, error) { + b := st.code + st.code = []byte{} + return b, nil +} + // return 0-indexed frame number where key is defined. -1 if not defined func(st *State) frameOf(key string) int { for i, m := range st.Cache { @@ -408,4 +427,3 @@ func(st *State) resetCurrent() { st.sink = nil st.CacheMap = make(map[string]string) } - diff --git a/go/vm/vm.go b/go/vm/vm.go index 4f62374..5de4205 100644 --- a/go/vm/vm.go +++ b/go/vm/vm.go @@ -29,34 +29,43 @@ func argFromBytes(input []byte) (string, []byte, error) { // If the router indicates an argument input, the optional argument is set on the state. // // TODO: the bytecode load is a separate step so Run should be run separately. -func Apply(input []byte, instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) { +func Apply(input []byte, instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { var err error + log.Printf("running input %v against instruction %v", input, instruction) arg, input, err := argFromBytes(input) if err != nil { - return st, input, err + return input, err } - rt := router.FromBytes(input) + rt := router.FromBytes(instruction) sym := rt.Get(arg) if sym == "" { sym = rt.Default() st.PutArg(arg) - } - if sym == "" { - instruction = NewLine([]byte{}, MOVE, []string{"_catch"}, nil, nil) - } else if sym == "_" { - instruction = NewLine([]byte{}, BACK, nil, nil, nil) - } else { - new_instruction := NewLine([]byte{}, MOVE, []string{sym}, nil, nil) - instruction = append(new_instruction, instruction...) } - st, instruction, err = Run(instruction, st, rs, ctx) - if err != nil { - return st, instruction, err + if sym == "" { + instruction = NewLine([]byte{}, MOVE, []string{"_catch"}, nil, nil) + } else { + instruction, err = rs.GetCode(sym) + if err != nil { + return instruction, err + } + + if sym == "_" { + instruction = NewLine([]byte{}, BACK, nil, nil, nil) + } else { + new_instruction := NewLine([]byte{}, MOVE, []string{sym}, nil, nil) + instruction = append(new_instruction, instruction...) + } } - return st, instruction, nil + + instruction, err = Run(instruction, st, rs, ctx) + if err != nil { + return instruction, err + } + return instruction, nil } // Run extracts individual op codes and arguments and executes them. @@ -64,61 +73,54 @@ func Apply(input []byte, instruction []byte, st state.State, rs resource.Resourc // Each step may update the state. // // On error, the remaining instructions will be returned. State will not be rolled back. -func Run(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) { +func Run(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { var err error for len(instruction) > 0 { log.Printf("instruction is now %v", instruction) op := binary.BigEndian.Uint16(instruction[:2]) if op > _MAX { - return st, instruction, fmt.Errorf("opcode value %v out of range (%v)", op, _MAX) + return instruction, fmt.Errorf("opcode value %v out of range (%v)", op, _MAX) } switch op { case CATCH: - st, instruction, err = RunCatch(instruction[2:], st, rs, ctx) - break + instruction, err = RunCatch(instruction[2:], st, rs, ctx) case CROAK: - st, instruction, err = RunCroak(instruction[2:], st, rs, ctx) - break + instruction, err = RunCroak(instruction[2:], st, rs, ctx) case LOAD: - st, instruction, err = RunLoad(instruction[2:], st, rs, ctx) - break + instruction, err = RunLoad(instruction[2:], st, rs, ctx) case RELOAD: - st, instruction, err = RunReload(instruction[2:], st, rs, ctx) - break + instruction, err = RunReload(instruction[2:], st, rs, ctx) case MAP: - st, instruction, err = RunMap(instruction[2:], st, rs, ctx) - break + instruction, err = RunMap(instruction[2:], st, rs, ctx) case MOVE: - st, instruction, err = RunMove(instruction[2:], st, rs, ctx) - break + instruction, err = RunMove(instruction[2:], st, rs, ctx) case BACK: - st, instruction, err = RunBack(instruction[2:], st, rs, ctx) - break + instruction, err = RunBack(instruction[2:], st, rs, ctx) default: err = fmt.Errorf("Unhandled state: %v", op) } if err != nil { - return st, instruction, err + return instruction, err } } - return st, instruction, nil + return instruction, nil } // RunMap executes the MAP opcode -func RunMap(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) { +func RunMap(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { head, tail, err := instructionSplit(instruction) if err != nil { - return st, instruction, err + return instruction, err } err = st.Map(head) - return st, tail, err + return tail, err } // RunMap executes the CATCH opcode -func RunCatch(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) { +func RunCatch(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { head, tail, err := instructionSplit(instruction) if err != nil { - return st, instruction, err + return instruction, err } bitFieldSize := tail[0] bitField := tail[1:1+bitFieldSize] @@ -128,69 +130,69 @@ func RunCatch(instruction []byte, st state.State, rs resource.Resource, ctx cont st.Down(head) tail = []byte{} } - return st, tail, nil + return tail, nil } // RunMap executes the CROAK opcode -func RunCroak(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) { +func RunCroak(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { head, tail, err := instructionSplit(instruction) if err != nil { - return st, instruction, err + return instruction, err } _ = head _ = tail st.Reset() - return st, []byte{}, nil + return []byte{}, nil } // RunLoad executes the LOAD opcode -func RunLoad(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) { +func RunLoad(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { head, tail, err := instructionSplit(instruction) if err != nil { - return st, instruction, err + return instruction, err } if !st.Check(head) { - return st, instruction, fmt.Errorf("key %v already loaded", head) + return instruction, fmt.Errorf("key %v already loaded", head) } sz := uint16(tail[0]) tail = tail[1:] r, err := refresh(head, rs, ctx) if err != nil { - return st, tail, err + return tail, err } err = st.Add(head, r, sz) - return st, tail, err + return tail, err } // RunLoad executes the RELOAD opcode -func RunReload(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) { +func RunReload(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { head, tail, err := instructionSplit(instruction) if err != nil { - return st, instruction, err + return instruction, err } r, err := refresh(head, rs, ctx) if err != nil { - return st, tail, err + return tail, err } st.Update(head, r) - return st, tail, nil + return tail, nil } // RunLoad executes the MOVE opcode -func RunMove(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) { +func RunMove(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { head, tail, err := instructionSplit(instruction) if err != nil { - return st, instruction, err + return instruction, err } st.Down(head) - return st, tail, nil + return tail, nil } // RunLoad executes the BACK opcode -func RunBack(instruction []byte, st state.State, rs resource.Resource, ctx context.Context) (state.State, []byte, error) { +func RunBack(instruction []byte, st *state.State, rs resource.Resource, ctx context.Context) ([]byte, error) { st.Up() - return st, instruction, nil + return instruction, nil } // retrieve data for key @@ -199,6 +201,9 @@ func refresh(key string, rs resource.Resource, ctx context.Context) (string, err if err != nil { return "", err } + if fn == nil { + return "", fmt.Errorf("no retrieve function for external symbol %v", key) + } return fn(ctx) } diff --git a/go/vm/vm_test.go b/go/vm/vm_test.go index 568268c..7f7acb3 100644 --- a/go/vm/vm_test.go +++ b/go/vm/vm_test.go @@ -96,17 +96,16 @@ func TestRun(t *testing.T) { rs := TestResource{} b := []byte{0x00, MOVE, 0x03} b = append(b, []byte("foo")...) - r, _, err := Run(b, st, &rs, context.TODO()) + _, err := Run(b, &st, &rs, context.TODO()) if err != nil { t.Errorf("error on valid opcode: %v", err) } b = []byte{0x01, 0x02} - r, _, err = Run(b, st, &rs, context.TODO()) + _, err = Run(b, &st, &rs, context.TODO()) if err == nil { t.Errorf("no error on invalid opcode") } - _ = r } func TestRunLoadRender(t *testing.T) { @@ -117,7 +116,7 @@ func TestRunLoadRender(t *testing.T) { ins := append([]byte{uint8(len(sym))}, []byte(sym)...) ins = append(ins, 0x0a) var err error - st, _, err = RunLoad(ins, st, &rs, context.TODO()) + _, err = RunLoad(ins, &st, &rs, context.TODO()) if err != nil { t.Error(err) } @@ -142,7 +141,7 @@ func TestRunLoadRender(t *testing.T) { sym = "two" ins = append([]byte{uint8(len(sym))}, []byte(sym)...) ins = append(ins, 0) - st, _, err = RunLoad(ins, st, &rs, context.TODO()) + _, err = RunLoad(ins, &st, &rs, context.TODO()) if err != nil { t.Error(err) } @@ -166,7 +165,7 @@ func TestRunMultiple(t *testing.T) { b := []byte{} b = NewLine(b, LOAD, []string{"one"}, nil, []uint8{0}) b = NewLine(b, LOAD, []string{"two"}, nil, []uint8{42}) - st, _, err := Run(b, st, &rs, context.TODO()) + _, err := Run(b, &st, &rs, context.TODO()) if err != nil { t.Error(err) } @@ -178,7 +177,7 @@ func TestRunReload(t *testing.T) { b := []byte{} b = NewLine(b, LOAD, []string{"dyn"}, nil, []uint8{0}) b = NewLine(b, MAP, []string{"dyn"}, nil, nil) - st, _, err := Run(b, st, &rs, context.TODO()) + _, err := Run(b, &st, &rs, context.TODO()) if err != nil { t.Error(err) } @@ -192,7 +191,7 @@ func TestRunReload(t *testing.T) { dynVal = "baz" b = []byte{} b = NewLine(b, RELOAD, []string{"dyn"}, nil, nil) - st, _, err = Run(b, st, &rs, context.TODO()) + _, err = Run(b, &st, &rs, context.TODO()) if err != nil { t.Error(err) } @@ -209,14 +208,15 @@ func TestRunReload(t *testing.T) { func TestRunArg(t *testing.T) { st := state.NewState(5) + rs := TestResource{} rt := router.NewRouter() rt.Add("foo", "bar") rt.Add("baz", "xyzzy") b := []byte{0x03} b = append(b, []byte("baz")...) - b = append(b, rt.ToBytes()...) + //b = append(b, rt.ToBytes()...) var err error - st, b, err = Apply(b, []byte{}, st, nil, context.TODO()) + b, err = Apply(b, rt.ToBytes(), &st, &rs, context.TODO()) if err != nil { t.Error(err) } @@ -237,9 +237,9 @@ func TestRunArgInvalid(t *testing.T) { rt.Add("baz", "xyzzy") b := []byte{0x03} b = append(b, []byte("bar")...) - b = append(b, rt.ToBytes()...) + //b = append(b, rt.ToBytes()...) var err error - st, b, err = Apply(b, []byte{}, st, nil, context.TODO()) + b, err = Apply(b, rt.ToBytes(), &st, nil, context.TODO()) if err != nil { t.Error(err) } @@ -253,97 +253,108 @@ func TestRunArgInvalid(t *testing.T) { } } -func TestRunArgInstructions(t *testing.T) { - st := state.NewState(5) - rs := TestResource{} - - rt := router.NewRouter() - rt.Add("foo", "bar") - b := []byte{0x03} - b = append(b, []byte("foo")...) - b = append(b, rt.ToBytes()...) - - bi := NewLine([]byte{}, LOAD, []string{"one"}, nil, []uint8{0}) - bi = NewLine(bi, LOAD, []string{"two"}, nil, []uint8{3}) - bi = NewLine(bi, MAP, []string{"one"}, nil, nil) - bi = NewLine(bi, MAP, []string{"two"}, nil, nil) - var err error - st, b, err = Apply(b, bi, st, &rs, 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) - } - loc := st.Where() - if loc != "bar" { - t.Errorf("expected where-state _catch, got %v", loc) - } - m, err := st.Get() - if err != nil { - t.Error(err) - } - r, err := rs.RenderTemplate(loc, m) - if err != nil { - t.Error(err) - } - _ = r -} - -func TestRunMoveAndBack(t *testing.T) { - st := state.NewState(5) - rs := TestResource{} - rt := router.NewRouter() - rt.Add("foo", "bar") - b := []byte{0x03} - b = append(b, []byte("foo")...) - b = append(b, rt.ToBytes()...) - bi := NewLine([]byte{}, LOAD, []string{"one"}, nil, []uint8{0}) - - var err error - st, b, err = Apply(b, bi, st, &rs, 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) - } - - rt = router.NewRouter() - rt.Add("foo", "baz") - b = []byte{0x03} - b = append(b, []byte("foo")...) - b = append(b, rt.ToBytes()...) - bi = NewLine([]byte{}, LOAD, []string{"two"}, nil, []uint8{0}) - st, b, err = Apply(b, bi, st, &rs, 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) - } - - rt = router.NewRouter() - rt.Add("foo", "_") - b = []byte{0x03} - b = append(b, []byte("foo")...) - b = append(b, rt.ToBytes()...) - st, b, err = Apply(b, []byte{}, st, &rs, 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) - } - loc := st.Where() - if loc != "bar" { - t.Errorf("expected where-string 'bar', got %v", loc) - } -} +//func TestRunArgInstructions(t *testing.T) { +// st := state.NewState(5) +// rs := TestResource{} +// +// rt := router.NewRouter() +// rt.Add("foo", "bar") +// b := []byte{0x03} +// b = append(b, []byte("foo")...) +// b = append(b, rt.ToBytes()...) +// +// bi := NewLine([]byte{}, LOAD, []string{"one"}, nil, []uint8{0}) +// bi = NewLine(bi, LOAD, []string{"two"}, nil, []uint8{3}) +// bi = NewLine(bi, MAP, []string{"one"}, nil, nil) +// bi = NewLine(bi, MAP, []string{"two"}, nil, nil) +// var err error +// b, err = Apply(b, bi, &st, &rs, 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) +// } +// loc := st.Where() +// if loc != "bar" { +// t.Errorf("expected where-state bar, got %v", loc) +// } +// m, err := st.Get() +// if err != nil { +// t.Fatal(err) +// } +// _, err = rs.RenderTemplate(loc, m) +// if err == nil { +// t.Fatalf("expected error to generate template") +// } +// _, err = Run(bi, &st, &rs, context.TODO()) +// if err != nil { +// t.Error(err) +// } +// m, err = st.Get() +// if err != nil { +// t.Fatal(err) +// } +// _, err = rs.RenderTemplate(loc, m) +// if err != nil { +// t.Fatal(err) +// } +//} +// +//func TestRunMoveAndBack(t *testing.T) { +// st := state.NewState(5) +// rs := TestResource{} +// rt := router.NewRouter() +// rt.Add("foo", "bar") +// b := []byte{0x03} +// b = append(b, []byte("foo")...) +// //b = append(b, rt.ToBytes()...) +// bi := NewLine([]byte{}, LOAD, []string{"one"}, nil, []uint8{0}) +// +// var err error +// b, err = Apply(b, bi, &st, &rs, 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) +// } +// +// rt = router.NewRouter() +// rt.Add("foo", "baz") +// b = []byte{0x03} +// b = append(b, []byte("foo")...) +// b = append(b, rt.ToBytes()...) +// bi = NewLine([]byte{}, LOAD, []string{"two"}, nil, []uint8{0}) +// b, err = Apply(b, bi, &st, &rs, 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) +// } +// +// rt = router.NewRouter() +// rt.Add("foo", "_") +// b = []byte{0x03} +// b = append(b, []byte("foo")...) +// //b = append(b, rt.ToBytes()...) +// b, err = Apply(b, rt.ToBytes(), &st, &rs, 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) +// } +// loc := st.Where() +// if loc != "bar" { +// t.Errorf("expected where-string 'bar', got %v", loc) +// } +//} func TestCatchAndBack(t *testing.T) { st := state.NewState(5) @@ -353,7 +364,7 @@ func TestCatchAndBack(t *testing.T) { b := NewLine([]byte{}, LOAD, []string{"one"}, nil, []uint8{0}) b = NewLine(b, CATCH, []string{"bar"}, []byte{0x04}, nil) b = NewLine(b, MOVE, []string{"foo"}, nil, nil) - st, _, err := Run(b, st, &rs, context.TODO()) + _, err := Run(b, &st, &rs, context.TODO()) if err != nil { t.Error(err) } @@ -366,7 +377,7 @@ func TestCatchAndBack(t *testing.T) { b = NewLine([]byte{}, LOAD, []string{"two"}, nil, []uint8{0}) b = NewLine(b, CATCH, []string{"bar"}, []byte{0x04}, nil) b = NewLine(b, MOVE, []string{"foo"}, nil, nil) - st, _, err = Run(b, st, &rs, context.TODO()) + _, err = Run(b, &st, &rs, context.TODO()) if err != nil { t.Error(err) }