Add a custom vm.Run() function #9
2 changed files with 133 additions and 1 deletions
58
covertest/run.go
Normal file
58
covertest/run.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package covertest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InstrHash maps instruction with scripthash of a contract it belongs to.
|
||||||
|
type InstrHash struct {
|
||||||
|
Offset int
|
||||||
|
Instruction opcode.Opcode
|
||||||
|
ScriptHash util.Uint160
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run starts execution of the loaded program and accumulates all seen opcodes
|
||||||
|
// together with the scripthash of a contract they belong to.
|
||||||
|
// Original vm.Run(): https://github.com/nspcc-dev/neo-go/blob/v0.101.3/pkg/vm/vm.go#L418
|
||||||
|
func Run(v *vm.VM) ([]InstrHash, error) {
|
||||||
fyrchik marked this conversation as resolved
|
|||||||
|
if !v.Ready() {
|
||||||
|
return nil, errors.New("no program loaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.HasFailed() {
|
||||||
|
// VM already ran something and failed, in general its state is
|
||||||
|
// undefined in this case so we can't run anything.
|
||||||
|
return nil, errors.New("VM has failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// vmstate.Halt (the default) or vmstate.Break are safe to continue.
|
||||||
|
var ops []InstrHash
|
||||||
|
for {
|
||||||
|
switch {
|
||||||
|
case v.HasFailed():
|
||||||
|
// Should be caught and reported already by the v.Step(),
|
||||||
|
// but we're checking here anyway just in case.
|
||||||
|
return ops, errors.New("VM has failed")
|
||||||
|
case v.HasHalted(), v.AtBreakpoint():
|
||||||
|
// Normal exit from this loop.
|
||||||
|
return ops, nil
|
||||||
|
case v.State() == vmstate.None:
|
||||||
|
nStr, curInstr := v.Context().NextInstr()
|
||||||
|
ops = append(ops, InstrHash{
|
||||||
|
Offset: nStr,
|
||||||
|
Instruction: curInstr,
|
||||||
|
ScriptHash: v.Context().ScriptHash(),
|
||||||
|
})
|
||||||
|
if err := v.Step(); err != nil {
|
||||||
|
return ops, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return ops, errors.New("unknown state")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,26 @@
|
||||||
package contract
|
package contract
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"math/rand"
|
||||||
"path"
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/contract-coverage-primer/covertest"
|
"git.frostfs.info/TrueCloudLab/contract-coverage-primer/covertest"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
const ctrPath = "../impulse"
|
const ctrPath = "../impulse"
|
||||||
|
|
||||||
// Key for tests
|
// keys for tests
|
||||||
var (
|
var (
|
||||||
validKey = []byte{1, 2, 3, 4, 5}
|
validKey = []byte{1, 2, 3, 4, 5}
|
||||||
invalidKey = []byte{1, 2, 3}
|
invalidKey = []byte{1, 2, 3}
|
||||||
|
@ -41,3 +49,69 @@ func TestContract(t *testing.T) {
|
||||||
inv.InvokeFail(t, "Invalid key size", "putNumber", invalidKey, 42)
|
inv.InvokeFail(t, "Invalid key size", "putNumber", invalidKey, 42)
|
||||||
inv.InvokeFail(t, "Invalid key size", "getNumber", invalidKey)
|
inv.InvokeFail(t, "Invalid key size", "getNumber", invalidKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRun(t *testing.T) {
|
||||||
|
e := newExecutor(t)
|
||||||
|
ctrDI := covertest.CompileFile(t, e.CommitteeHash, ctrPath, path.Join(ctrPath, "config.yml"))
|
||||||
|
e.DeployContract(t, ctrDI.Contract, nil)
|
||||||
fyrchik marked this conversation as resolved
fyrchik
commented
`101` and `2` look like magic constants. Can we calculate them somehow?
elebedeva
commented
Fixed Fixed
|
|||||||
|
|
||||||
|
startOffsetPutNumber, err := getStartOffset(ctrDI.DebugInfo, "PutNumber")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
hasResult, err := hasResult(ctrDI.DebugInfo, "PutNumber")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
someNum := getNumToPut()
|
||||||
|
|
||||||
|
// set up a VM for covertest.Run()
|
||||||
|
covertestRunVM := setUpVMForPut(t, e, ctrDI.Contract, hasResult, startOffsetPutNumber, someNum, invalidKey)
|
||||||
|
res, covErr := covertest.Run(covertestRunVM)
|
||||||
|
t.Log("Printing collected instructions:")
|
||||||
|
spew.Dump(res)
|
||||||
|
t.Log("covertest.Run() returned an error: ", covErr)
|
||||||
|
|
||||||
|
// set up a VM for vm.Run()
|
||||||
|
origRunVM := setUpVMForPut(t, e, ctrDI.Contract, hasResult, startOffsetPutNumber, someNum, invalidKey)
|
||||||
|
runerr := origRunVM.Run()
|
||||||
|
t.Log("vm.Run() returned an error: ", covErr)
|
||||||
|
|
||||||
|
// check if errors are the same
|
||||||
|
require.Equal(t, runerr.Error(), covErr.Error())
|
||||||
|
|
||||||
|
// check if the number of elements on the stack is the same
|
||||||
|
require.Equal(t, origRunVM.Estack().Len(), covertestRunVM.Estack().Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
func setUpVMForPut(t *testing.T, e *neotest.Executor, contract *neotest.Contract, hasResult bool, methodOff int, num int, key []byte) (v *vm.VM) {
|
||||||
|
ic, err := e.Chain.GetTestVM(trigger.Application, nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
ic.VM.LoadNEFMethod(contract.NEF, contract.Hash, contract.Hash, callflag.All, hasResult, methodOff, -1, nil)
|
||||||
|
ic.VM.Context().Estack().PushVal(num)
|
||||||
|
ic.VM.Context().Estack().PushVal(key)
|
||||||
|
return ic.VM
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStartOffset(di *compiler.DebugInfo, methodID string) (int, error) {
|
||||||
|
for _, method := range di.Methods {
|
||||||
|
if method.ID == methodID {
|
||||||
|
return int(method.Range.Start), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, errors.New("Method not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasResult(di *compiler.DebugInfo, methodID string) (bool, error) {
|
||||||
|
for _, method := range di.Methods {
|
||||||
|
if method.ID == methodID {
|
||||||
|
if method.ReturnType == "Void" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, errors.New("Method not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNumToPut() int {
|
||||||
|
return rand.Intn(100)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue
First line of the function should not be empty (there is a linter for this, we just don't have it in this repo)
Fixed