neotest: POC of test coverage

Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
Evgeniy Stratonikov 2022-06-29 17:05:31 +03:00
parent 1f62ecd5a3
commit 96b5d90894
4 changed files with 115 additions and 7 deletions

View file

@ -335,7 +335,7 @@ func (e *Executor) AddNewBlock(t testing.TB, txs ...*transaction.Transaction) *b
return b return b
} }
// GenerateNewBlocks adds the specified number of empty blocks to the chain. // GenerateNewBlocks adds the specified number of empty Blocks to the chain.
func (e *Executor) GenerateNewBlocks(t testing.TB, count int) []*block.Block { func (e *Executor) GenerateNewBlocks(t testing.TB, count int) []*block.Block {
blocks := make([]*block.Block, count) blocks := make([]*block.Block, count)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {

View file

@ -1,6 +1,7 @@
package neotest package neotest
import ( import (
"fmt"
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
@ -48,8 +49,12 @@ func (e *Executor) ValidatorInvoker(h util.Uint160) *ContractInvoker {
// TestInvoke creates test the VM and invokes the method with the args. // TestInvoke creates test the VM and invokes the method with the args.
func (c *ContractInvoker) TestInvoke(t testing.TB, method string, args ...interface{}) (*vm.Stack, error) { func (c *ContractInvoker) TestInvoke(t testing.TB, method string, args ...interface{}) (*vm.Stack, error) {
fmt.Println("TEST INVOKE")
tx := c.PrepareInvokeNoSign(t, method, args...) tx := c.PrepareInvokeNoSign(t, method, args...)
b := c.NewUnsignedBlock(t, tx) b := c.NewUnsignedBlock(t, tx)
calculateCoverage(t, c.Chain, tx, b)
ic := c.Chain.GetTestVM(trigger.Application, tx, b) ic := c.Chain.GetTestVM(trigger.Application, tx, b)
t.Cleanup(ic.Finalize) t.Cleanup(ic.Finalize)

View file

@ -2,6 +2,7 @@ package neotest
import ( import (
"io" "io"
"path/filepath"
"testing" "testing"
"github.com/nspcc-dev/neo-go/cli/smartcontract" "github.com/nspcc-dev/neo-go/cli/smartcontract"
@ -16,9 +17,10 @@ import (
// Contract contains contract info for deployment. // Contract contains contract info for deployment.
type Contract struct { type Contract struct {
Hash util.Uint160 Hash util.Uint160
NEF *nef.File NEF *nef.File
Manifest *manifest.Manifest Manifest *manifest.Manifest
DebugInfo *compiler.DebugInfo
} }
// contracts caches the compiled contracts from FS across multiple tests. // contracts caches the compiled contracts from FS across multiple tests.
@ -44,6 +46,11 @@ func CompileSource(t testing.TB, sender util.Uint160, src io.Reader, opts *compi
// CompileFile compiles a contract from the file and returns its NEF, manifest and hash. // CompileFile compiles a contract from the file and returns its NEF, manifest and hash.
func CompileFile(t testing.TB, sender util.Uint160, srcPath string, configPath string) *Contract { func CompileFile(t testing.TB, sender util.Uint160, srcPath string, configPath string) *Contract {
absPath, err := filepath.Abs(srcPath)
if err == nil {
srcPath = absPath
}
if c, ok := contracts[srcPath]; ok { if c, ok := contracts[srcPath]; ok {
return c return c
} }
@ -72,9 +79,10 @@ func CompileFile(t testing.TB, sender util.Uint160, srcPath string, configPath s
require.NoError(t, err) require.NoError(t, err)
c := &Contract{ c := &Contract{
Hash: state.CreateContractHash(sender, ne.Checksum, m.Name), Hash: state.CreateContractHash(sender, ne.Checksum, m.Name),
NEF: ne, NEF: ne,
Manifest: m, Manifest: m,
DebugInfo: di,
} }
contracts[srcPath] = c contracts[srcPath] = c
return c return c

95
pkg/neotest/coverage.go Normal file
View file

@ -0,0 +1,95 @@
package neotest
import (
"fmt"
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"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/util"
)
var (
Blocks map[string][]testing.CoverBlock
Counters map[string][]uint32
)
func init() {
var cover testing.Cover
cover.Mode = testing.CoverMode()
Blocks = make(map[string][]testing.CoverBlock)
Counters = make(map[string][]uint32)
cover.Blocks = Blocks
cover.Counters = Counters
testing.RegisterCover(cover)
}
func calculateCoverage(t testing.TB, bc blockchainer.Blockchainer, tx *transaction.Transaction, b *block.Block) {
fmt.Println("CALCULATE COVERAGE")
ic := bc.GetTestVM(trigger.Application, tx, b)
t.Cleanup(ic.Finalize)
ic.VM.LoadWithFlags(tx.Script, callflag.All)
hm := make(map[util.Uint160][]int)
runLoop:
for {
switch {
case ic.VM.HasStopped():
break runLoop
default:
h := ic.VM.GetCurrentScriptHash()
if err := ic.VM.Step(); err != nil {
break runLoop
}
if ic.VM.Context() != nil {
hm[h] = append(hm[h], ic.VM.Context().IP())
}
}
}
// Calculate coverage.
coverageLoop:
for h := range hm {
fmt.Println("TRY HASH", h)
for name, c := range contracts {
fmt.Println("TRY CONTRACT", name, c.Hash)
if !c.Hash.Equals(h) {
continue
}
if c.DebugInfo == nil {
continue coverageLoop
}
var coverBlocks []testing.CoverBlock
var coverCounts []uint32
ipLoop:
for _, ip := range hm[h] {
for _, m := range c.DebugInfo.Methods {
for _, sp := range m.SeqPoints {
if sp.Opcode == ip {
fmt.Println("FOUND COVER BLOCK")
coverCounts = append(coverCounts, 1)
coverBlocks = append(coverBlocks, testing.CoverBlock{
Line0: uint32(sp.StartLine),
Col0: uint16(sp.StartCol),
Line1: uint32(sp.EndLine),
Col1: uint16(sp.EndCol),
Stmts: 1,
})
continue ipLoop
}
}
}
}
Blocks[name] = coverBlocks
Counters[name] = coverCounts
continue coverageLoop
}
}
}