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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/rand"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"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/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/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const ctrPath = "../impulse"
|
||||
|
||||
// Key for tests
|
||||
// keys for tests
|
||||
var (
|
||||
validKey = []byte{1, 2, 3, 4, 5}
|
||||
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", "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